diff options
Diffstat (limited to 'tools/node_modules/expresso/deps/jscoverage/js/js.cpp')
-rw-r--r-- | tools/node_modules/expresso/deps/jscoverage/js/js.cpp | 4050 |
1 files changed, 4050 insertions, 0 deletions
diff --git a/tools/node_modules/expresso/deps/jscoverage/js/js.cpp b/tools/node_modules/expresso/deps/jscoverage/js/js.cpp new file mode 100644 index 0000000..9ffc847 --- /dev/null +++ b/tools/node_modules/expresso/deps/jscoverage/js/js.cpp @@ -0,0 +1,4050 @@ +/* -*- 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 ***** */ + +/* + * JS shell. + */ +#include "jsstddef.h" +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <locale.h> +#include "jstypes.h" +#include "jsarena.h" +#include "jsutil.h" +#include "jsprf.h" +#include "jsapi.h" +#include "jsarray.h" +#include "jsatom.h" +#include "jsbuiltins.h" +#include "jscntxt.h" +#include "jsdbgapi.h" +#include "jsemit.h" +#include "jsfun.h" +#include "jsgc.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsparse.h" +#include "jsscope.h" +#include "jsscript.h" + +#ifdef LIVECONNECT +#include "jsjava.h" +#endif + +#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 */ + +#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 + +typedef enum JSShellExitCode { + EXITCODE_RUNTIME_ERROR = 3, + EXITCODE_FILE_NOT_FOUND = 4, + EXITCODE_OUT_OF_MEMORY = 5 +} JSShellExitCode; + +size_t gStackChunkSize = 8192; + +/* Assume that we can not use more than 5e5 bytes of C stack by default. */ +static size_t gMaxStackSize = 500000; +static jsuword gStackBase; + +static size_t gScriptStackQuota = JS_DEFAULT_SCRIPT_STACK_QUOTA; + +static JSBool gEnableBranchCallback = JS_FALSE; +static uint32 gBranchCount; +static uint32 gBranchLimit; + +int gExitCode = 0; +JSBool gQuitting = JS_FALSE; +FILE *gErrFile = NULL; +FILE *gOutFile = NULL; + +static JSBool reportWarnings = JS_TRUE; +static JSBool compileOnly = JS_FALSE; + +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 const JSErrorFormatString * +my_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber); +static JSObject * +split_setup(JSContext *cx); + +#ifdef EDITLINE +JS_BEGIN_EXTERN_C +extern char *readline(const char *prompt); +extern void add_history(char *line); +JS_END_EXTERN_C +#endif + +static JSBool +GetLine(JSContext *cx, char *bufp, FILE *file, const char *prompt) { +#ifdef EDITLINE + /* + * Use readline only if file is stdin, because there's no way to specify + * another handle. Are other filehandles interactive? + */ + if (file == stdin) { + char *linep = readline(prompt); + if (!linep) + return JS_FALSE; + if (linep[0] != '\0') + add_history(linep); + strcpy(bufp, linep); + JS_free(cx, linep); + bufp += strlen(bufp); + *bufp++ = '\n'; + *bufp = '\0'; + } else +#endif + { + char line[256]; + fprintf(gOutFile, prompt); + fflush(gOutFile); + if (!fgets(line, sizeof line, file)) + return JS_FALSE; + strcpy(bufp, line); + } + return JS_TRUE; +} + +static JSBool +my_BranchCallback(JSContext *cx, JSScript *script) +{ + if (++gBranchCount == gBranchLimit) { + if (script) { + if (script->filename) + fprintf(gErrFile, "%s:", script->filename); + fprintf(gErrFile, "%u: script branch callback (%u callbacks)\n", + script->lineno, gBranchLimit); + } else { + fprintf(gErrFile, "native branch callback (%u callbacks)\n", + gBranchLimit); + } + gBranchCount = 0; + return JS_FALSE; + } +#ifdef JS_THREADSAFE + if ((gBranchCount & 0xff) == 1) { +#endif + if ((gBranchCount & 0x3fff) == 1) + JS_MaybeGC(cx); +#ifdef JS_THREADSAFE + else + JS_YieldRequest(cx); + } +#endif + return JS_TRUE; +} + +static void +SetContextOptions(JSContext *cx) +{ + jsuword stackLimit; + + if (gMaxStackSize == 0) { + /* + * Disable checking for stack overflow if limit is zero. + */ + stackLimit = 0; + } else { +#if JS_STACK_GROWTH_DIRECTION > 0 + stackLimit = gStackBase + gMaxStackSize; +#else + stackLimit = gStackBase - gMaxStackSize; +#endif + } + JS_SetThreadStackLimit(cx, stackLimit); + JS_SetScriptStackQuota(cx, gScriptStackQuota); + if (gEnableBranchCallback) { + JS_SetBranchCallback(cx, my_BranchCallback); + JS_ToggleOptions(cx, JSOPTION_NATIVE_BRANCH_CALLBACK); + } +} + +static void +Process(JSContext *cx, JSObject *obj, char *filename, JSBool forceTTY) +{ + JSBool ok, hitEOF; + JSScript *script; + jsval result; + JSString *str; + char buffer[4096]; + char *bufp; + 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); + + if (!forceTTY && !isatty(fileno(file))) { + /* + * 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); + + oldopts = JS_GetOptions(cx); + JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL); + script = JS_CompileFileHandle(cx, obj, filename, file); + JS_SetOptions(cx, oldopts); + if (script) { + if (!compileOnly) + (void)JS_ExecuteScript(cx, obj, script, NULL); + JS_DestroyScript(cx, script); + } + + if (file != stdin) + fclose(file); + return; + } + + /* It's an interactive filehandle; drop into read-eval-print loop. */ + lineno = 1; + hitEOF = JS_FALSE; + do { + bufp = buffer; + *bufp = '\0'; + + /* + * 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; + do { + if (!GetLine(cx, bufp, file, startline == lineno ? "js> " : "")) { + hitEOF = JS_TRUE; + break; + } + bufp += strlen(bufp); + lineno++; + } while (!JS_BufferIsCompilableUnit(cx, obj, buffer, strlen(buffer))); + + /* Clear any pending exception from previous failed compiles. */ + JS_ClearPendingException(cx); + script = JS_CompileScript(cx, obj, buffer, strlen(buffer), "typein", + startline); + if (script) { + if (!compileOnly) { + ok = JS_ExecuteScript(cx, obj, script, &result); + if (ok && !JSVAL_IS_VOID(result)) { + str = JS_ValueToString(cx, result); + if (str) + fprintf(gOutFile, "%s\n", JS_GetStringBytes(str)); + else + ok = JS_FALSE; + } + } + JS_DestroyScript(cx, script); + } + } while (!hitEOF && !gQuitting); + fprintf(gOutFile, "\n"); + if (file != stdin) + fclose(file); + return; +} + +static int +usage(void) +{ + fprintf(gErrFile, "%s\n", JS_GetImplementationVersion()); + fprintf(gErrFile, "usage: js [-zKPswWxCi] [-b branchlimit] [-c stackchunksize] [-o option] [-v version] [-f scriptfile] [-e script] [-S maxstacksize] " +#ifdef JS_GC_ZEAL +"[-Z gczeal] " +#endif +"[scriptfile] [scriptarg...]\n"); + return 2; +} + +static struct { + const char *name; + uint32 flag; +} js_options[] = { + {"strict", JSOPTION_STRICT}, + {"werror", JSOPTION_WERROR}, + {"atline", JSOPTION_ATLINE}, + {"xml", JSOPTION_XML}, + {"relimit", JSOPTION_RELIMIT}, + {"anonfunfix", JSOPTION_ANONFUNFIX}, + {"jit", JSOPTION_JIT}, + {NULL, 0} +}; + +extern JSClass global_class; + +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 'b': + case 'c': + case 'f': + case 'e': + case 'v': + case 'S': +#ifdef JS_GC_ZEAL + case 'Z': +#endif + ++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 + + 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 'j': + JS_ToggleOptions(cx, JSOPTION_JIT); +#if defined(JS_TRACER) && defined(DEBUG) +extern struct JSClass jitstats_class; +extern void js_InitJITStatsClass(JSContext *cx, JSObject *glob); + js_InitJITStatsClass(cx, JS_GetGlobalObject(cx)); + JS_DefineObject(cx, JS_GetGlobalObject(cx), "tracemonkey", + &jitstats_class, NULL, 0); +#endif + break; + + case 'o': + if (++i == argc) + return usage(); + + for (j = 0; js_options[j].name; ++j) { + if (strcmp(js_options[j].name, argv[i]) == 0) { + JS_ToggleOptions(cx, js_options[j].flag); + break; + } + } + break; + + case 'P': + if (JS_GET_CLASS(cx, JS_GetPrototype(cx, obj)) != &global_class) { + JSObject *gobj; + + if (!JS_SealObject(cx, obj, JS_TRUE)) + return JS_FALSE; + gobj = JS_NewObject(cx, &global_class, NULL, NULL); + 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 'b': + gBranchLimit = atoi(argv[++i]); + gEnableBranchCallback = (gBranchLimit != 0); + 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); + + /* + * 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 'z': + obj = split_setup(cx); + if (!obj) + return gExitCode; + break; +#ifdef MOZ_SHARK + case 'k': + JS_ConnectShark(); + break; +#endif + default: + return usage(); + } + } + + if (filename || isInteractive) + Process(cx, obj, filename, forceTTY); + return gExitCode; +} + +static JSBool +Version(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + if (argc > 0 && JSVAL_IS_INT(argv[0])) + *rval = INT_TO_JSVAL(JS_SetVersion(cx, (JSVersion) JSVAL_TO_INT(argv[0]))); + else + *rval = INT_TO_JSVAL(JS_GetVersion(cx)); + return JS_TRUE; +} + +static JSBool +Options(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uint32 optset, flag; + uintN i, j, found; + JSString *str; + const char *opt; + char *names; + + optset = 0; + for (i = 0; i < argc; i++) { + str = JS_ValueToString(cx, argv[i]); + if (!str) + return JS_FALSE; + opt = JS_GetStringBytes(str); + for (j = 0; js_options[j].name; j++) { + if (strcmp(js_options[j].name, opt) == 0) { + optset |= js_options[j].flag; + break; + } + } + } + optset = JS_ToggleOptions(cx, optset); + + names = NULL; + found = 0; + while (optset != 0) { + flag = optset; + optset &= optset - 1; + flag &= ~optset; + for (j = 0; js_options[j].name; j++) { + if (js_options[j].flag == flag) { + names = JS_sprintf_append(names, "%s%s", + names ? "," : "", js_options[j].name); + found++; + break; + } + } + } + if (!found) + names = strdup(""); + if (!names) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + + str = JS_NewString(cx, names, strlen(names)); + if (!str) { + free(names); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +static JSBool +Load(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uintN i; + JSString *str; + const char *filename; + JSScript *script; + JSBool ok; + uint32 oldopts; + + for (i = 0; i < argc; i++) { + str = JS_ValueToString(cx, argv[i]); + if (!str) + return JS_FALSE; + argv[i] = STRING_TO_JSVAL(str); + filename = JS_GetStringBytes(str); + errno = 0; + oldopts = JS_GetOptions(cx); + JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL); + script = JS_CompileFile(cx, obj, filename); + JS_SetOptions(cx, oldopts); + if (!script) { + ok = JS_FALSE; + } else { + ok = !compileOnly + ? JS_ExecuteScript(cx, obj, script, NULL) + : JS_TRUE; + JS_DestroyScript(cx, script); + } + if (!ok) + return JS_FALSE; + } + + return JS_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_NewString(cx, buf, sawNewline ? buflength - 1 : buflength); + if (!str) { + JS_free(cx, buf); + return JS_FALSE; + } + + *vp = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +#ifdef JS_TRACER +static jsval JS_FASTCALL +Print_tn(JSContext *cx, JSString *str) +{ + char *bytes = JS_EncodeString(cx, str); + if (!bytes) + return JSVAL_ERROR_COOKIE; + fprintf(gOutFile, "%s\n", bytes); + JS_free(cx, bytes); + fflush(gOutFile); + return JSVAL_VOID; +} +#endif + +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, JSObject *obj, uintN argc, jsval *argv, jsval *rval); + +static JSBool +Quit(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ +#ifdef LIVECONNECT + JSJ_SimpleShutdown(); +#endif + + JS_ConvertArguments(cx, argc, argv,"/ i", &gExitCode); + + gQuitting = JS_TRUE; + return JS_FALSE; +} + +static JSBool +GC(JSContext *cx, uintN argc, jsval *vp) +{ + JSRuntime *rt; + uint32 preBytes; + + rt = cx->runtime; + preBytes = rt->gcBytes; + JS_GC(cx); + + fprintf(gOutFile, "before %lu, after %lu, break %08lx\n", + (unsigned long)preBytes, (unsigned long)rt->gcBytes, +#ifdef XP_UNIX + (unsigned long)sbrk(0) +#else + 0 +#endif + ); +#ifdef JS_GCMETER + js_DumpGCStats(rt, stdout); +#endif + *vp = JSVAL_VOID; + return JS_TRUE; +} + +static JSBool +GCParameter(JSContext *cx, uintN argc, jsval *vp) +{ + JSString *str; + const char *paramName; + JSGCParamKey param; + uint32 value; + + 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); + } + paramName = JS_GetStringBytes(str); + if (!paramName) + return JS_FALSE; + if (strcmp(paramName, "maxBytes") == 0) { + param = JSGC_MAX_BYTES; + } else if (strcmp(paramName, "maxMallocBytes") == 0) { + param = JSGC_MAX_MALLOC_BYTES; + } else { + JS_ReportError(cx, + "the first argument argument must be either maxBytes " + "or maxMallocBytes"); + return JS_FALSE; + } + + if (!JS_ValueToECMAUint32(cx, argc < 2 ? JSVAL_VOID : vp[3], &value)) + return JS_FALSE; + if (value == 0) { + JS_ReportError(cx, + "the second argument must be convertable to uint32 with " + "non-zero value"); + 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; + char *bytes; + JSCountHeapTracer countTracer; + JSCountHeapNode *node; + size_t counter; + + static const struct { + const char *name; + int32 kind; + } traceKindNames[] = { + { "all", -1 }, + { "object", JSTRACE_OBJECT }, + { "double", JSTRACE_DOUBLE }, + { "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 (v != JSVAL_NULL) { + 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; + bytes = JS_GetStringBytes(str); + if (!bytes) + return JS_FALSE; + for (i = 0; ;) { + if (strcmp(bytes, traceKindNames[i].name) == 0) { + traceKind = traceKindNames[i].kind; + break; + } + if (++i == JS_ARRAY_LENGTH(traceKindNames)) { + JS_ReportError(cx, "trace kind name '%s' is unknown", bytes); + 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 JSScript * +ValueToScript(JSContext *cx, jsval v) +{ + JSScript *script; + JSFunction *fun; + + if (!JSVAL_IS_PRIMITIVE(v) && + JS_GET_CLASS(cx, JSVAL_TO_OBJECT(v)) == &js_ScriptClass) { + script = (JSScript *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v)); + } else { + 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 +GetTrapArgs(JSContext *cx, uintN argc, jsval *argv, JSScript **scriptp, + int32 *ip) +{ + jsval v; + uintN intarg; + JSScript *script; + + *scriptp = cx->fp->down->script; + *ip = 0; + if (argc != 0) { + v = argv[0]; + intarg = 0; + if (!JSVAL_IS_PRIMITIVE(v) && + (JS_GET_CLASS(cx, JSVAL_TO_OBJECT(v)) == &js_FunctionClass || + JS_GET_CLASS(cx, JSVAL_TO_OBJECT(v)) == &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, + void *closure) +{ + JSString *str; + JSStackFrame *caller; + + str = (JSString *) closure; + caller = JS_GetScriptedCaller(cx, NULL); + if (!JS_EvaluateScript(cx, caller->scopeChain, + JS_GetStringBytes(str), JS_GetStringLength(str), + 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, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + JSScript *script; + int32 i; + + 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; + return JS_SetTrap(cx, script, script->code + i, TrapHandler, str); +} + +static JSBool +Untrap(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSScript *script; + int32 i; + + if (!GetTrapArgs(cx, argc, argv, &script, &i)) + return JS_FALSE; + JS_ClearTrap(cx, script, script->code + i, NULL, NULL); + return JS_TRUE; +} + +static JSBool +LineToPC(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSScript *script; + int32 i; + uintN lineno; + jsbytecode *pc; + + if (argc == 0) { + JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_LINE2PC_USAGE); + return JS_FALSE; + } + script = cx->fp->down->script; + if (!GetTrapArgs(cx, argc, argv, &script, &i)) + return JS_FALSE; + lineno = (i == 0) ? script->lineno : (uintN)i; + pc = JS_LineNumberToPC(cx, script, lineno); + if (!pc) + return JS_FALSE; + *rval = INT_TO_JSVAL(PTRDIFF(pc, script->code, jsbytecode)); + return JS_TRUE; +} + +static JSBool +PCToLine(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSScript *script; + int32 i; + uintN lineno; + + if (!GetTrapArgs(cx, argc, argv, &script, &i)) + return JS_FALSE; + lineno = JS_PCToLineNumber(cx, script, script->code + i); + if (!lineno) + return JS_FALSE; + *rval = INT_TO_JSVAL(lineno); + return JS_TRUE; +} + +#ifdef DEBUG + +static void +UpdateSwitchTableBounds(JSScript *script, uintN offset, + uintN *start, uintN *end) +{ + jsbytecode *pc; + JSOp op; + ptrdiff_t jmplen; + jsint low, high, n; + + pc = script->code + offset; + op = (JSOp) *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) +{ + uintN offset, delta, caseOff, switchTableStart, switchTableEnd; + jssrcnote *notes, *sn; + JSSrcNoteType type; + const char *name; + uint32 index; + JSAtom *atom; + JSString *str; + + fprintf(gOutFile, "\nSource notes:\n"); + offset = 0; + notes = SCRIPT_NOTES(script); + 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(script->code[offset] == JSOP_NOP); + } + } + fprintf(gOutFile, "%3u: %5u [%4u] %-8s", + (uintN) PTRDIFF(sn, notes, jssrcnote), offset, delta, name); + switch (type) { + case SRC_SETLINE: + fprintf(gOutFile, " lineno %u", (uintN) js_GetSrcNoteOffset(sn, 0)); + break; + case SRC_FOR: + fprintf(gOutFile, " 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: + fprintf(gOutFile, " 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: + fprintf(gOutFile, " 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); + JS_GET_SCRIPT_ATOM(script, index, atom); + JS_ASSERT(ATOM_IS_STRING(atom)); + str = ATOM_TO_STRING(atom); + fprintf(gOutFile, " atom %u (", index); + js_FileEscapedString(gOutFile, str, 0); + putc(')', gOutFile); + break; + case SRC_FUNCDEF: { + const char *bytes; + JSObject *obj; + JSFunction *fun; + + index = js_GetSrcNoteOffset(sn, 0); + JS_GET_SCRIPT_OBJECT(script, index, obj); + fun = (JSFunction *) JS_GetPrivate(cx, obj); + str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT); + bytes = str ? JS_GetStringBytes(str) : "N/A"; + fprintf(gOutFile, " function %u (%s)", index, bytes); + break; + } + case SRC_SWITCH: + fprintf(gOutFile, " length %u", (uintN) js_GetSrcNoteOffset(sn, 0)); + caseOff = (uintN) js_GetSrcNoteOffset(sn, 1); + if (caseOff) + fprintf(gOutFile, " first case offset %u", caseOff); + UpdateSwitchTableBounds(script, offset, + &switchTableStart, &switchTableEnd); + break; + case SRC_CATCH: + delta = (uintN) js_GetSrcNoteOffset(sn, 0); + if (delta) { + if (script->main[offset] == JSOP_LEAVEBLOCK) + fprintf(gOutFile, " stack depth %u", delta); + else + fprintf(gOutFile, " guard delta %u", delta); + } + break; + default:; + } + fputc('\n', gOutFile); + } +} + +static JSBool +Notes(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uintN i; + JSScript *script; + + for (i = 0; i < argc; i++) { + script = ValueToScript(cx, argv[i]); + if (!script) + continue; + + SrcNotes(cx, script); + } + 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) +{ + JSTryNote *tn, *tnlimit; + + if (script->trynotesOffset == 0) + return JS_TRUE; + + tn = JS_SCRIPT_TRYNOTES(script)->vector; + tnlimit = tn + JS_SCRIPT_TRYNOTES(script)->length; + fprintf(gOutFile, "\nException table:\n" + "kind stack start end\n"); + do { + JS_ASSERT(tn->kind < JS_ARRAY_LENGTH(TryNoteNames)); + fprintf(gOutFile, " %-7s %6u %8u %8u\n", + TryNoteNames[tn->kind], tn->stackDepth, + tn->start, tn->start + tn->length); + } while (++tn != tnlimit); + return JS_TRUE; +} + +static JSBool +Disassemble(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSBool lines; + uintN i; + JSScript *script; + + if (argc > 0 && + JSVAL_IS_STRING(argv[0]) && + !strcmp(JS_GetStringBytes(JSVAL_TO_STRING(argv[0])), "-l")) { + lines = JS_TRUE; + argv++, argc--; + } else { + lines = JS_FALSE; + } + for (i = 0; i < argc; i++) { + script = ValueToScript(cx, argv[i]); + if (!script) + return JS_FALSE; + if (VALUE_IS_FUNCTION(cx, argv[i])) { + JSFunction *fun = JS_ValueToFunction(cx, argv[i]); + if (fun && (fun->flags & JSFUN_FLAGS_MASK)) { + uint16 flags = fun->flags; + fputs("flags:", stdout); + +#define SHOW_FLAG(flag) if (flags & JSFUN_##flag) fputs(" " #flag, stdout); + + SHOW_FLAG(LAMBDA); + SHOW_FLAG(SETTER); + SHOW_FLAG(GETTER); + SHOW_FLAG(BOUND_METHOD); + SHOW_FLAG(HEAVYWEIGHT); + SHOW_FLAG(THISP_STRING); + SHOW_FLAG(THISP_NUMBER); + SHOW_FLAG(THISP_BOOLEAN); + SHOW_FLAG(EXPR_CLOSURE); + SHOW_FLAG(INTERPRETED); + +#undef SHOW_FLAG + putchar('\n'); + } + } + + if (!js_Disassemble(cx, script, lines, stdout)) + return JS_FALSE; + SrcNotes(cx, script); + TryNotes(cx, script); + } + return JS_TRUE; +} + +static JSBool +DisassFile(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + const char *filename; + JSScript *script; + JSBool ok; + uint32 oldopts; + + if (!argc) + return JS_TRUE; + + str = JS_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + argv[0] = STRING_TO_JSVAL(str); + + filename = JS_GetStringBytes(str); + oldopts = JS_GetOptions(cx); + JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO | JSOPTION_NO_SCRIPT_RVAL); + script = JS_CompileFile(cx, obj, filename); + JS_SetOptions(cx, oldopts); + if (!script) + return JS_FALSE; + + obj = JS_NewScriptObject(cx, script); + if (!obj) + return JS_FALSE; + + *rval = OBJECT_TO_JSVAL(obj); /* I like to root it, root it. */ + ok = Disassemble(cx, obj, 1, rval, rval); /* gross, but works! */ + *rval = JSVAL_VOID; + + return ok; +} + +static JSBool +DisassWithSrc(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ +#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; + 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; + + /* burn the leading lines */ + line2 = JS_PCToLineNumber(cx, script, pc); + for (line1 = 0; line1 < line2 - 1; line1++) + fgets(linebuf, LINE_BUF_LEN, file); + + bupline = 0; + while (pc < end) { + line2 = JS_PCToLineNumber(cx, script, pc); + + if (line2 < line1) { + if (bupline != line2) { + bupline = line2; + fprintf(gOutFile, "%s %3u: BACKUP\n", sep, line2); + } + } else { + if (bupline && line1 == line2) + fprintf(gOutFile, "%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++; + fprintf(gOutFile, "%s %3u: %s", sep, line1, linebuf); + } + } + + len = js_Disassemble1(cx, script, pc, + PTRDIFF(pc, script->code, jsbytecode), + JS_TRUE, stdout); + if (!len) { + ok = JS_FALSE; + goto bail; + } + pc += len; + } + + bail: + fclose(file); + } + return ok; +#undef LINE_BUF_LEN +} + +static JSBool +Tracing(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSBool bval; + JSString *str; + +#if JS_THREADED_INTERP + JS_ReportError(cx, "tracing not supported in JS_THREADED_INTERP builds"); + return JS_FALSE; +#else + if (argc == 0) { + *rval = BOOLEAN_TO_JSVAL(cx->tracefp != 0); + return JS_TRUE; + } + + switch (JS_TypeOfValue(cx, argv[0])) { + case JSTYPE_NUMBER: + bval = JSVAL_IS_INT(argv[0]) + ? JSVAL_TO_INT(argv[0]) + : (jsint) *JSVAL_TO_DOUBLE(argv[0]); + break; + case JSTYPE_BOOLEAN: + bval = JSVAL_TO_BOOLEAN(argv[0]); + break; + default: + str = JS_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + JS_ReportError(cx, "tracing: illegal argument %s", + JS_GetStringBytes(str)); + return JS_FALSE; + } + cx->tracefp = bval ? stderr : NULL; + return JS_TRUE; +#endif +} + +static void +DumpScope(JSContext *cx, JSObject *obj, FILE *fp) +{ + uintN i; + JSScope *scope; + JSScopeProperty *sprop; + jsval v; + JSString *str; + + i = 0; + scope = OBJ_SCOPE(obj); + for (sprop = SCOPE_LAST_PROP(scope); sprop; sprop = sprop->parent) { + if (SCOPE_HAD_MIDDLE_DELETE(scope) && !SCOPE_HAS_PROPERTY(scope, sprop)) + continue; + fprintf(fp, "%3u %p ", i, (void *)sprop); + + v = ID_TO_VALUE(sprop->id); + if (JSID_IS_INT(sprop->id)) { + fprintf(fp, "[%ld]", (long)JSVAL_TO_INT(v)); + } else { + if (JSID_IS_ATOM(sprop->id)) { + str = JSVAL_TO_STRING(v); + } else { + JS_ASSERT(JSID_IS_OBJECT(sprop->id)); + str = js_ValueToString(cx, v); + fputs("object ", fp); + } + if (!str) + fputs("<error>", fp); + else + js_FileEscapedString(fp, str, '"'); + } +#define DUMP_ATTR(name) if (sprop->attrs & JSPROP_##name) fputs(" " #name, fp) + DUMP_ATTR(ENUMERATE); + DUMP_ATTR(READONLY); + DUMP_ATTR(PERMANENT); + DUMP_ATTR(GETTER); + DUMP_ATTR(SETTER); +#undef DUMP_ATTR + + fprintf(fp, " slot %lu flags %x shortid %d\n", + (unsigned long)sprop->slot, sprop->flags, sprop->shortid); + } +} + +static JSBool +DumpStats(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uintN i; + JSString *str; + const char *bytes; + jsid id; + JSObject *obj2; + JSProperty *prop; + jsval value; + + for (i = 0; i < argc; i++) { + str = JS_ValueToString(cx, argv[i]); + if (!str) + return JS_FALSE; + argv[i] = STRING_TO_JSVAL(str); + bytes = JS_GetStringBytes(str); + if (strcmp(bytes, "arena") == 0) { +#ifdef JS_ARENAMETER + JS_DumpArenaStats(stdout); +#endif + } else if (strcmp(bytes, "atom") == 0) { + js_DumpAtoms(cx, gOutFile); + } else if (strcmp(bytes, "global") == 0) { + DumpScope(cx, cx->globalObject, stdout); + } else { + if (!JS_ValueToId(cx, STRING_TO_JSVAL(str), &id)) + return JS_FALSE; + if (!js_FindProperty(cx, id, &obj, &obj2, &prop)) + return JS_FALSE; + if (prop) { + OBJ_DROP_PROPERTY(cx, obj2, prop); + if (!OBJ_GET_PROPERTY(cx, obj, id, &value)) + return JS_FALSE; + } + if (!prop || !JSVAL_IS_OBJECT(value)) { + fprintf(gErrFile, "js: invalid stats argument %s\n", + bytes); + continue; + } + obj = JSVAL_TO_OBJECT(value); + if (obj) + DumpScope(cx, obj, stdout); + } + } + return JS_TRUE; +} + +static JSBool +DumpHeap(JSContext *cx, uintN argc, jsval *vp) +{ + char *fileName; + jsval v; + void* startThing; + uint32 startTraceKind; + const char *badTraceArg; + void *thingToFind; + size_t maxDepth; + void *thingToIgnore; + FILE *dumpFile; + JSBool ok; + + fileName = NULL; + if (argc > 0) { + v = JS_ARGV(cx, vp)[0]; + if (v != JSVAL_NULL) { + JSString *str; + + str = JS_ValueToString(cx, v); + if (!str) + return JS_FALSE; + JS_ARGV(cx, vp)[0] = STRING_TO_JSVAL(str); + fileName = JS_GetStringBytes(str); + } + } + + 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 (v != JSVAL_NULL) { + 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 (v != JSVAL_NULL) { + badTraceArg = "toFind"; + goto not_traceable_arg; + } + } + + maxDepth = (size_t)-1; + if (argc > 3) { + v = JS_ARGV(cx, vp)[3]; + if (v != JSVAL_NULL) { + 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 (v != JSVAL_NULL) { + 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; +} + +#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, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSBool b = JS_FALSE; + jschar c = 0; + int32 i = 0, j = 0; + uint32 u = 0; + jsdouble d = 0, I = 0, re = 0, im = 0; + char *s = NULL; + 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, argv, "b/ciujdIsSWofvZZ*", + &b, &c, &i, &u, &j, &d, &I, &s, &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); + fprintf(gOutFile, + "d %g, I %g, s %s, S %s, W %s, obj %s, fun %s\n" + "v %s, re %g, im %g\n", + d, I, s, str ? JS_GetStringBytes(str) : "", EscapeWideString(w), + JS_GetStringBytes(JS_ValueToString(cx, OBJECT_TO_JSVAL(obj2))), + fun ? JS_GetStringBytes(JS_DecompileFunction(cx, fun, 4)) : "", + JS_GetStringBytes(JS_ValueToString(cx, v)), re, im); + 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, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + if (argc != 0 && !JS_ValueToObject(cx, argv[0], &obj)) + return JS_FALSE; + JS_ClearScope(cx, obj); + return JS_TRUE; +} + +static JSBool +Intern(JSContext *cx, uintN argc, jsval *vp) +{ + JSString *str; + + str = JS_ValueToString(cx, argc == 0 ? JSVAL_VOID : vp[2]); + if (!str) + return JS_FALSE; + if (!JS_InternUCStringN(cx, JS_GetStringChars(str), + JS_GetStringLength(str))) { + return JS_FALSE; + } + *vp = JSVAL_VOID; + return JS_TRUE; +} + +static JSBool +Clone(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFunction *fun; + JSObject *funobj, *parent, *clone; + + 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; + *rval = OBJECT_TO_JSVAL(clone); + return JS_TRUE; +} + +static JSBool +Seal(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSObject *target; + JSBool deep = JS_FALSE; + + if (!JS_ConvertArguments(cx, argc, argv, "o/b", &target, &deep)) + return JS_FALSE; + if (!target) + return JS_TRUE; + return JS_SealObject(cx, target, deep); +} + +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) + 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++) { + 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, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + *rval = 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, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + int32 mode = 1; + jschar chars[20]; + size_t charsLength = 5; + char bytes[20]; + size_t bytesLength = 20; + if (argc && !JS_ValueToInt32(cx, *argv, &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; + } + return !JS_IsExceptionPending (cx); +} + +static JSBool +ThrowError(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + 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, jsval 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 OBJ_DEFINE_PROPERTY(cx, cpx->inner, asId, *vp, NULL, NULL, + JSPROP_ENUMERATE, NULL); + } + return JS_TRUE; +} + +static JSBool +split_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + ComplexObject *cpx; + + cpx = split_get_private(cx, obj); + if (!cpx) + return JS_TRUE; + if (!cpx->isInner && cpx->inner) { + if (JSVAL_IS_STRING(id)) { + JSString *str; + + str = JSVAL_TO_STRING(id); + return JS_GetUCProperty(cx, cpx->inner, JS_GetStringChars(str), + JS_GetStringLength(str), vp); + } + if (JSVAL_IS_INT(id)) + return JS_GetElement(cx, cpx->inner, JSVAL_TO_INT(id), vp); + return JS_TRUE; + } + + return JS_TRUE; +} + +static JSBool +split_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + ComplexObject *cpx; + + cpx = split_get_private(cx, obj); + if (!cpx) + return JS_TRUE; + if (!cpx->isInner && cpx->inner) { + if (JSVAL_IS_STRING(id)) { + JSString *str; + + str = JSVAL_TO_STRING(id); + return JS_SetUCProperty(cx, cpx->inner, JS_GetStringChars(str), + JS_GetStringLength(str), vp); + } + if (JSVAL_IS_INT(id)) + return JS_SetElement(cx, cpx->inner, JSVAL_TO_INT(id), vp); + return JS_TRUE; + } + + return JS_TRUE; +} + +static JSBool +split_delProperty(JSContext *cx, JSObject *obj, jsval 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 OBJ_DELETE_PROPERTY(cx, cpx->inner, asId, vp); + } + 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: + 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 = JSVAL_ZERO; + break; + + case JSENUMERATE_NEXT: + iterator = (JSObject*)JSVAL_TO_OBJECT(*statep); + if (!JS_NextProperty(cx, iterator, idp)) + return JS_FALSE; + + if (!JSVAL_IS_VOID(*idp)) + break; + /* Fall through. */ + + case JSENUMERATE_DESTROY: + /* Let GC at our iterator object. */ + *statep = JSVAL_NULL; + break; + } + + return JS_TRUE; +} + +static JSBool +split_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags, + JSObject **objp) +{ + ComplexObject *cpx; + + cpx = split_get_private(cx, obj); + if (!cpx) + return JS_TRUE; + if (!cpx->isInner && cpx->inner) { + jsid asId; + JSProperty *prop; + + if (!JS_ValueToId(cx, id, &asId)) + return JS_FALSE; + + if (!OBJ_LOOKUP_PROPERTY(cx, cpx->inner, asId, objp, &prop)) + return JS_FALSE; + if (prop) + OBJ_DROP_PROPERTY(cx, cpx->inner, prop); + + return JS_TRUE; + } + +#ifdef LAZY_STANDARD_CLASSES + if (!(flags & JSRESOLVE_ASSIGNING)) { + JSBool resolved; + + if (!JS_ResolveStandardClass(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 uint32 +split_mark(JSContext *cx, JSObject *obj, void *arg) +{ + ComplexObject *cpx; + + cpx = (ComplexObject *) JS_GetPrivate(cx, obj); + + if (!cpx->isInner && cpx->inner) { + /* Mark the inner object. */ + JS_MarkGCThing(cx, cpx->inner, "ComplexObject.inner", arg); + } + + return 0; +} + +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_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 JSExtendedClass split_global_class = { + {"split_global", + JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE | JSCLASS_HAS_PRIVATE | + JSCLASS_GLOBAL_FLAGS | JSCLASS_IS_EXTENDED, + split_addProperty, split_delProperty, + split_getProperty, split_setProperty, + (JSEnumerateOp)split_enumerate, + (JSResolveOp)split_resolve, + JS_ConvertStub, split_finalize, + NULL, NULL, NULL, NULL, NULL, NULL, + split_mark, NULL}, + NULL, split_outerObject, split_innerObject, + NULL, NULL, NULL, NULL, NULL +}; + +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_NewObject(cx, &split_global_class.base, NULL, NULL); + if (!obj || !JS_SetParent(cx, obj, NULL)) { + 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(JS_GET_CLASS(cx, outer) == &split_global_class.base); + + 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_NewObject(cx, &split_global_class.base, NULL, NULL); + if (!obj || !JS_SetParent(cx, obj, NULL) || !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 (JS_GET_CLASS(cx, obj) == &split_global_class.base) + 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) || !JS_ValueToBoolean(cx, v, &b)) + return JS_FALSE; + return !b || JS_EnumerateStandardClasses(cx, obj); +} + +static JSBool +sandbox_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags, + JSObject **objp) +{ + jsval v; + JSBool b, resolved; + + if (!JS_GetProperty(cx, obj, "lazy", &v) || !JS_ValueToBoolean(cx, v, &b)) + return JS_FALSE; + if (b && (flags & JSRESOLVE_ASSIGNING) == 0) { + if (!JS_ResolveStandardClass(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, + JS_PropertyStub, JS_PropertyStub, + JS_PropertyStub, JS_PropertyStub, + sandbox_enumerate, (JSResolveOp)sandbox_resolve, + JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +static JSBool +EvalInContext(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + JSObject *sobj; + JSContext *scx; + const jschar *src; + size_t srclen; + JSBool lazy, ok; + jsval v; + JSStackFrame *fp; + + sobj = NULL; + if (!JS_ConvertArguments(cx, argc, argv, "S / o", &str, &sobj)) + return JS_FALSE; + + scx = JS_NewContext(JS_GetRuntime(cx), gStackChunkSize); + if (!scx) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + +#ifdef JS_THREADSAFE + JS_BeginRequest(scx); +#endif + src = JS_GetStringChars(str); + srclen = JS_GetStringLength(str); + lazy = JS_FALSE; + if (srclen == 4 && + src[0] == 'l' && src[1] == 'a' && src[2] == 'z' && src[3] == 'y') { + lazy = JS_TRUE; + srclen = 0; + } + + if (!sobj) { + sobj = JS_NewObject(scx, &sandbox_class, NULL, NULL); + if (!sobj || (!lazy && !JS_InitStandardClasses(scx, sobj))) { + ok = JS_FALSE; + goto out; + } + v = BOOLEAN_TO_JSVAL(lazy); + ok = JS_SetProperty(cx, sobj, "lazy", &v); + if (!ok) + goto out; + } + + if (srclen == 0) { + *rval = OBJECT_TO_JSVAL(sobj); + ok = JS_TRUE; + } else { + fp = JS_GetScriptedCaller(cx, NULL); + JS_SetGlobalObject(scx, sobj); + JS_ToggleOptions(scx, JSOPTION_DONT_REPORT_UNCAUGHT); + ok = JS_EvaluateUCScript(scx, sobj, src, srclen, + fp->script->filename, + JS_PCToLineNumber(cx, fp->script, + fp->regs->pc), + rval); + if (!ok) { + if (JS_GetPendingException(scx, &v)) + JS_SetPendingException(cx, v); + else + JS_ReportOutOfMemory(cx); + } + } + +out: +#ifdef JS_THREADSAFE + JS_EndRequest(scx); +#endif + JS_DestroyContext(scx); + return ok; +} + +static int32 JS_FASTCALL +ShapeOf_tn(JSObject *obj) +{ + if (!obj) + return 0; + if (!OBJ_IS_NATIVE(obj)) + return -1; + return OBJ_SHAPE(obj); +} + +static JSBool +ShapeOf(JSContext *cx, uintN argc, jsval *vp) +{ + jsval v = JS_ARGV(cx, vp)[0]; + if (!JSVAL_IS_OBJECT(v)) { + JS_ReportError(cx, "shapeOf: object expected"); + return JS_FALSE; + } + return JS_NewNumberValue(cx, ShapeOf_tn(JSVAL_TO_OBJECT(v)), vp); +} + +#ifdef JS_THREADSAFE + +static JSBool +Sleep_fn(JSContext *cx, uintN argc, jsval *vp) +{ + jsdouble t_secs; + PRUint32 t_ticks; + jsrefcount rc; + + if (!JS_ValueToNumber(cx, argc == 0 ? JSVAL_VOID : vp[2], &t_secs)) + return JS_FALSE; + + if (t_secs < 0 || JSDOUBLE_IS_NaN(t_secs)) + t_secs = 0; + + rc = JS_SuspendRequest(cx); + t_ticks = (PRUint32)(PR_TicksPerSecond() * t_secs); + if (PR_Sleep(t_ticks) == PR_SUCCESS) + *vp = JSVAL_TRUE; + else + *vp = JSVAL_FALSE; + JS_ResumeRequest(cx, rc); + return JS_TRUE; +} + +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) +{ + ScatterThreadData *td; + ScatterStatus st; + JSContext *cx; + + td = (ScatterThreadData *)arg; + cx = td->cx; + + /* Wait for go 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 go. */ + JS_SetContextThread(cx); + JS_SetThreadStackLimit(cx, 0); + 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; + ScatterData sd; + JSBool ok; + jsrefcount rc; + + if (!gEnableBranchCallback) { + /* Enable the branch callback, for periodic scope-sharing. */ + gEnableBranchCallback = JS_TRUE; + JS_SetBranchCallback(cx, my_BranchCallback); + JS_ToggleOptions(cx, JSOPTION_NATIVE_BRANCH_CALLBACK); + } + + 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_AddRoot(cx, &sd.results[i]); + if (!ok) { + while (i-- > 0) + JS_RemoveRoot(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_AddRoot(cx, &sd.threads[i].fn); + if (ok && !JS_GetElement(cx, inArr, (jsint) i, &sd.threads[i].fn)) { + JS_RemoveRoot(cx, &sd.threads[i].fn); + ok = JS_FALSE; + } + if (!ok) { + while (i-- > 0) + JS_RemoveRoot(cx, &sd.threads[i].fn); + free(sd.threads); + sd.threads = NULL; + goto fail; + } + } + + for (i = 1; i < n; i++) { + JSContext *newcx = JS_NewContext(JS_GetRuntime(cx), 8192); + if (!newcx) + goto fail; + JS_SetGlobalObject(newcx, JS_GetGlobalObject(cx)); + 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]); + + rc = JS_SuspendRequest(cx); + for (i = 1; i < n; i++) { + PR_JoinThread(sd.threads[i].thr); + } + JS_ResumeRequest(cx, rc); + +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_RemoveRoot(cx, &sd.threads[i].fn); + acx = sd.threads[i].cx; + if (acx) { + JS_SetContextThread(acx); + JS_DestroyContext(acx); + } + } + free(sd.threads); + } + if (sd.results) { + for (i = 0; i < n; i++) + JS_RemoveRoot(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; +} + +#endif + +JS_DEFINE_TRCINFO_1(Print, (2, (static, JSVAL_FAIL, Print_tn, CONTEXT, STRING, 0, 0))) +JS_DEFINE_TRCINFO_1(ShapeOf, (1, (static, INT32, ShapeOf_tn, OBJECT, 0, 0))) + +/* We use a mix of JS_FS and JS_FN to test both kinds of natives. */ +static JSFunctionSpec shell_functions[] = { + JS_FS("version", Version, 0,0,0), + JS_FS("options", Options, 0,0,0), + JS_FS("load", Load, 1,0,0), + JS_FN("readline", ReadLine, 0,0), + JS_TN("print", Print, 0,0, Print_trcinfo), + JS_FS("help", Help, 0,0,0), + JS_FS("quit", Quit, 0,0,0), + JS_FN("gc", GC, 0,0), + JS_FN("gcparam", GCParameter, 2,0), + JS_FN("countHeap", CountHeap, 0,0), +#ifdef JS_GC_ZEAL + JS_FN("gczeal", GCZeal, 1,0), +#endif + JS_FS("trap", Trap, 3,0,0), + JS_FS("untrap", Untrap, 2,0,0), + JS_FS("line2pc", LineToPC, 0,0,0), + JS_FS("pc2line", PCToLine, 0,0,0), + JS_FN("stackQuota", StackQuota, 0,0), + JS_FS("stringsAreUTF8", StringsAreUTF8, 0,0,0), + JS_FS("testUTF8", TestUTF8, 1,0,0), + JS_FS("throwError", ThrowError, 0,0,0), +#ifdef DEBUG + JS_FS("dis", Disassemble, 1,0,0), + JS_FS("disfile", DisassFile, 1,0,0), + JS_FS("dissrc", DisassWithSrc, 1,0,0), + JS_FN("dumpHeap", DumpHeap, 0,0), + JS_FS("notes", Notes, 1,0,0), + JS_FS("tracing", Tracing, 0,0,0), + JS_FS("stats", DumpStats, 1,0,0), +#endif +#ifdef TEST_CVTARGS + JS_FS("cvtargs", ConvertArgs, 0,0,12), +#endif + JS_FN("build", BuildDate, 0,0), + JS_FS("clear", Clear, 0,0,0), + JS_FN("intern", Intern, 1,0), + JS_FS("clone", Clone, 1,0,0), + JS_FS("seal", Seal, 1,0,1), + JS_FN("getpda", GetPDA, 1,0), + JS_FN("getslx", GetSLX, 1,0), + JS_FN("toint32", ToInt32, 1,0), + JS_FS("evalcx", EvalInContext, 1,0,0), + JS_TN("shapeOf", ShapeOf, 1,0, ShapeOf_trcinfo), +#ifdef MOZ_SHARK + JS_FS("startShark", js_StartShark, 0,0,0), + JS_FS("stopShark", js_StopShark, 0,0,0), + JS_FS("connectShark", js_ConnectShark, 0,0,0), + JS_FS("disconnectShark", js_DisconnectShark, 0,0,0), +#endif +#ifdef MOZ_CALLGRIND + JS_FS("startCallgrind", js_StartCallgrind, 0,0,0), + JS_FS("stopCallgrind", js_StopCallgrind, 0,0,0), + JS_FS("dumpCallgrind", js_DumpCallgrind, 1,0,0), +#endif +#ifdef MOZ_VTUNE + JS_FS("startVtune", js_StartVtune, 1,0,0), + JS_FS("stopVtune", js_StopVtune, 0,0,0), + JS_FS("pauseVtune", js_PauseVtune, 0,0,0), + JS_FS("resumeVtune", js_ResumeVtune, 0,0,0), +#endif +#ifdef DEBUG_ARRAYS + JS_FS("arrayInfo", js_ArrayInfo, 1,0,0), +#endif +#ifdef JS_THREADSAFE + JS_FN("sleep", Sleep_fn, 1,0), + JS_FN("scatter", Scatter, 1,0), +#endif + JS_FS_END +}; + +static const char shell_help_header[] = +"Command Description\n" +"======= ===========\n"; + +static const char *const shell_help_messages[] = { +"version([number]) Get or set JavaScript version number", +"options([option ...]) Get or toggle JavaScript options", +"load(['foo.js' ...]) Load files named by string arguments", +"readline() Read a single line from stdin", +"print([exp ...]) Evaluate and print expressions", +"help([name ...]) Display usage and help messages", +"quit() Quit the shell", +"gc() Run the garbage collector", +"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", +#ifdef JS_GC_ZEAL +"gczeal(level) How zealous the garbage collector should be", +#endif +"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 +"dis([fun]) Disassemble functions into bytecodes", +"disfile('foo.js') Disassemble script file into bytecodes", +"dissrc([fun]) Disassemble functions with source lines", +"dumpHeap([fileName[, start[, toFind[, maxDepth[, toIgnore]]]]])\n" +" Interface to JS_DumpHeap with output sent to file", +"notes([fun]) Show source notes for functions", +"tracing([toggle]) Turn tracing on or off", +"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", +"seal(obj[, deep]) Seal object, or object graph if deep", +"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", +"shapeOf(obj) Get the shape of obj (an implementation detail)", +#ifdef MOZ_SHARK +"startShark() Start a Shark session.\n" +" Shark must be running with programatic sampling.", +"stopShark() Stop a running Shark session.", +"connectShark() Connect to Shark.\n" +" The -k switch does this automatically.", +"disconnectShark() Disconnect from Shark.", +#endif +#ifdef MOZ_CALLGRIND +"startCallgrind() Start callgrind instrumentation.\n", +"stopCallgrind() Stop callgrind instumentation.", +"dumpCallgrind([name]) Dump callgrind counters.\n", +#endif +#ifdef MOZ_VTUNE +"startVtune([filename]) Start vtune instrumentation.\n", +"stopVtune() Stop vtune instumentation.", +"pauseVtune() Pause vtune collection.\n", +"resumeVtune() Resume vtune collection.\n", +#endif +#ifdef DEBUG_ARRAYS +"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 +}; + +/* Help messages must match shell functions. */ +JS_STATIC_ASSERT(JS_ARRAY_LENGTH(shell_help_messages) + 1 == + JS_ARRAY_LENGTH(shell_functions)); + +#ifdef DEBUG +static void +CheckHelpMessages() +{ + const char *const *m; + const char *lp; + + /* Each message must begin with "function_name(" prefix. */ + for (m = shell_help_messages; m != JS_ARRAY_END(shell_help_messages); ++m) { + lp = strchr(*m, '('); + JS_ASSERT(lp); + JS_ASSERT(memcmp(shell_functions[m - shell_help_messages].name, + *m, lp - *m) == 0); + } +} +#else +# define CheckHelpMessages() ((void) 0) +#endif + +static JSBool +Help(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + uintN i, j; + int did_header, did_something; + JSType type; + JSFunction *fun; + JSString *str; + const char *bytes; + + 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; + 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 ? ATOM_TO_STRING(fun->atom) : NULL; + } else if (type == JSTYPE_STRING) { + str = JSVAL_TO_STRING(argv[i]); + } else { + str = NULL; + } + if (str) { + bytes = JS_GetStringBytes(str); + for (j = 0; shell_functions[j].name; j++) { + if (!strcmp(bytes, 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; + fprintf(gErrFile, "Sorry, no help for %s\n", + JS_GetStringBytes(str)); + } + } + } + return JS_TRUE; +} + +static JSObject * +split_setup(JSContext *cx) +{ + JSObject *outer, *inner, *arguments; + + outer = split_create_outer(cx); + if (!outer) + return NULL; + JS_SetGlobalObject(cx, outer); + + inner = split_create_inner(cx, outer); + if (!inner) + return NULL; + + if (!JS_DefineFunctions(cx, inner, shell_functions)) + return NULL; + JS_ClearScope(cx, outer); + + /* 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; + } + +#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 +}; + +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}, + {NULL,0,0,NULL,NULL} +}; + +static JSBool +its_item(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + *rval = OBJECT_TO_JSVAL(obj); + if (argc != 0) + JS_SetCallReturnValue2(cx, argv[0]); + return JS_TRUE; +} + +static JSBool +its_bindMethod(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + char *name; + JSObject *method; + + if (!JS_ConvertArguments(cx, argc, argv, "so", &name, &method)) + return JS_FALSE; + + *rval = OBJECT_TO_JSVAL(method); + + if (JS_TypeOfValue(cx, *rval) != JSTYPE_FUNCTION) { + JSString *valstr = JS_ValueToString(cx, *rval); + if (valstr) { + JS_ReportError(cx, "can't bind method %s to non-callable object %s", + name, JS_GetStringBytes(valstr)); + } + return JS_FALSE; + } + + if (!JS_DefineProperty(cx, obj, name, *rval, NULL, NULL, JSPROP_ENUMERATE)) + return JS_FALSE; + + return JS_SetParent(cx, method, obj); +} + +static JSFunctionSpec its_methods[] = { + {"item", its_item, 0,0,0}, + {"bindMethod", its_bindMethod, 2,0,0}, + {NULL,NULL,0,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, jsval id, jsval *vp) +{ + if (its_noisy) { + fprintf(gOutFile, "adding its property %s,", + JS_GetStringBytes(JS_ValueToString(cx, id))); + fprintf(gOutFile, " initial value %s\n", + JS_GetStringBytes(JS_ValueToString(cx, *vp))); + } + return JS_TRUE; +} + +static JSBool +its_delProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + if (its_noisy) { + fprintf(gOutFile, "deleting its property %s,", + JS_GetStringBytes(JS_ValueToString(cx, id))); + fprintf(gOutFile, " current value %s\n", + JS_GetStringBytes(JS_ValueToString(cx, *vp))); + } + return JS_TRUE; +} + +static JSBool +its_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + if (its_noisy) { + fprintf(gOutFile, "getting its property %s,", + JS_GetStringBytes(JS_ValueToString(cx, id))); + fprintf(gOutFile, " current value %s\n", + JS_GetStringBytes(JS_ValueToString(cx, *vp))); + } + return JS_TRUE; +} + +static JSBool +its_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + char *str; + if (its_noisy) { + fprintf(gOutFile, "setting its property %s,", + JS_GetStringBytes(JS_ValueToString(cx, id))); + fprintf(gOutFile, " new value %s\n", + JS_GetStringBytes(JS_ValueToString(cx, *vp))); + } + + if (!JSVAL_IS_STRING(id)) + return JS_TRUE; + + str = JS_GetStringBytes(JSVAL_TO_STRING(id)); + if (!strcmp(str, "noisy")) + return JS_ValueToBoolean(cx, *vp, &its_noisy); + else if (!strcmp(str, "enum_fail")) + return 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: + 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 = JSVAL_ZERO; + 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 (!JSVAL_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, jsval id, uintN flags, + JSObject **objp) +{ + if (its_noisy) { + fprintf(gOutFile, "resolving its property %s, flags {%s,%s,%s}\n", + JS_GetStringBytes(JS_ValueToString(cx, id)), + (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) +{ + if (its_noisy) + fprintf(gOutFile, "finalizing it\n"); +} + +static JSClass its_class = { + "It", JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE, + 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 = PTRDIFF(report->tokenptr, report->linebuf, char); + 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, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSFunction *fun; + const char *name, **nargv; + uintN i, nargc; + JSString *str; + pid_t pid; + int status; + + fun = JS_ValueToFunction(cx, argv[-2]); + if (!fun) + return JS_FALSE; + if (!fun->atom) + return JS_TRUE; + name = JS_GetStringBytes(ATOM_TO_STRING(fun->atom)); + nargc = 1 + argc; + nargv = JS_malloc(cx, (nargc + 1) * sizeof(char *)); + if (!nargv) + return JS_FALSE; + nargv[0] = name; + for (i = 1; i < nargc; i++) { + str = JS_ValueToString(cx, argv[i-1]); + if (!str) { + JS_free(cx, nargv); + return JS_FALSE; + } + nargv[i] = JS_GetStringBytes(str); + } + nargv[nargc] = 0; + 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; + } + JS_free(cx, nargv); + return JS_TRUE; +} +#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, jsval id, uintN flags, + JSObject **objp) +{ +#ifdef LAZY_STANDARD_CLASSES + if ((flags & JSRESOLVE_ASSIGNING) == 0) { + JSBool resolved; + + if (!JS_ResolveStandardClass(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 | JSRESOLVE_ASSIGNING)) == 0) { + /* + * 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; + name = JS_GetStringBytes(JSVAL_TO_STRING(id)); + ok = JS_TRUE; + for (comp = strtok(path, ":"); comp; comp = strtok(NULL, ":")) { + if (*comp != '\0') { + full = JS_smprintf("%s/%s", comp, name); + 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, + JS_PropertyStub, JS_PropertyStub, + JS_PropertyStub, JS_PropertyStub, + global_enumerate, (JSResolveOp) global_resolve, + JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +static JSBool +env_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ +/* XXX porting may be easy, but these don't seem to supply setenv by default */ +#if !defined XP_BEOS && !defined XP_OS2 && !defined SOLARIS + JSString *idstr, *valstr; + const char *name, *value; + int rv; + + idstr = JS_ValueToString(cx, id); + valstr = JS_ValueToString(cx, *vp); + if (!idstr || !valstr) + return JS_FALSE; + name = JS_GetStringBytes(idstr); + value = JS_GetStringBytes(valstr); +#if defined XP_WIN || defined HPUX || defined OSF1 || defined IRIX + { + char *waste = JS_smprintf("%s=%s", name, value); + 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). + */ + free(waste); +#endif + } +#else + rv = setenv(name, value, 1); +#endif + if (rv < 0) { + JS_ReportError(cx, "can't set envariable %s to %s", name, value); + return JS_FALSE; + } + *vp = STRING_TO_JSVAL(valstr); +#endif /* !defined XP_BEOS && !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, jsval id, uintN flags, + JSObject **objp) +{ + JSString *idstr, *valstr; + const char *name, *value; + + if (flags & JSRESOLVE_ASSIGNING) + return JS_TRUE; + + idstr = JS_ValueToString(cx, id); + if (!idstr) + return JS_FALSE; + name = JS_GetStringBytes(idstr); + 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, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +#ifdef NARCISSUS + +static JSBool +defineProperty(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + JSString *str; + jsval value; + JSBool dontDelete, readOnly, dontEnum; + const jschar *chars; + size_t length; + uintN attrs; + + dontDelete = readOnly = dontEnum = JS_FALSE; + if (!JS_ConvertArguments(cx, argc, argv, "Sv/bbb", + &str, &value, &dontDelete, &readOnly, &dontEnum)) { + return JS_FALSE; + } + chars = JS_GetStringChars(str); + length = JS_GetStringLength(str); + attrs = dontEnum ? 0 : JSPROP_ENUMERATE; + if (dontDelete) + attrs |= JSPROP_PERMANENT; + if (readOnly) + attrs |= JSPROP_READONLY; + return JS_DefineUCProperty(cx, obj, chars, length, value, NULL, NULL, + attrs); +} + +static JSBool +Evaluate(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + /* function evaluate(source, filename, lineno) { ... } */ + JSString *source; + const char *filename = ""; + jsuint lineno = 0; + uint32 oldopts; + JSBool ok; + + if (argc == 0) { + *rval = JSVAL_VOID; + return JS_TRUE; + } + + if (!JS_ConvertArguments(cx, argc, argv, "S/su", + &source, &filename, &lineno)) { + return JS_FALSE; + } + + oldopts = JS_GetOptions(cx); + JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO); + ok = JS_EvaluateUCScript(cx, obj, JS_GetStringChars(source), + JS_GetStringLength(source), filename, + lineno, rval); + JS_SetOptions(cx, oldopts); + + return ok; +} + +#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 == '/' +#ifdef XP_WIN + || *cp == '\\' +#endif + ) { + 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; +} + +static JSBool +snarf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSString *str; + const char *filename; + char *pathname; + JSStackFrame *fp; + JSBool ok; + off_t cc, len; + char *buf; + FILE *file; + + str = JS_ValueToString(cx, argv[0]); + if (!str) + return JS_FALSE; + filename = JS_GetStringBytes(str); + + /* Get the currently executing script's name. */ + fp = JS_GetScriptedCaller(cx, NULL); + JS_ASSERT(fp && fp->script->filename); + pathname = MakeAbsolutePathname(cx, fp->script->filename, filename); + if (!pathname) + return JS_FALSE; + + ok = JS_FALSE; + len = 0; + buf = NULL; + file = fopen(pathname, "rb"); + if (!file) { + JS_ReportError(cx, "can't open %s: %s", pathname, strerror(errno)); + } else { + if (fseek(file, 0, SEEK_END) == EOF) { + JS_ReportError(cx, "can't seek end of %s", pathname); + } else { + len = ftell(file); + if (len == -1 || 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_free(cx, buf); + JS_ReportError(cx, "can't read %s: %s", pathname, + (cc < 0) ? strerror(errno) + : "short read"); + } else { + len = (size_t)cc; + ok = JS_TRUE; + } + } + } + } + fclose(file); + } + JS_free(cx, pathname); + if (!ok) { + JS_free(cx, buf); + return ok; + } + + buf[len] = '\0'; + str = JS_NewString(cx, buf, len); + if (!str) { + JS_free(cx, buf); + return JS_FALSE; + } + *rval = STRING_TO_JSVAL(str); + return JS_TRUE; +} + +#endif /* NARCISSUS */ + +static JSBool +ContextCallback(JSContext *cx, uintN contextOp) +{ + if (contextOp == JSCONTEXT_NEW) { + JS_SetErrorReporter(cx, my_ErrorReporter); + JS_SetVersion(cx, JSVERSION_LATEST); + SetContextOptions(cx); + } + return JS_TRUE; +} + +int +main(int argc, char **argv, char **envp) +{ + int stackDummy; + JSRuntime *rt; + JSContext *cx; + JSObject *glob, *it, *envobj; + int result; +#ifdef LIVECONNECT + JavaVM *java_vm = NULL; +#endif +#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 */ + + CheckHelpMessages(); + setlocale(LC_ALL, ""); + + gStackBase = (jsuword)&stackDummy; + +#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 + + gErrFile = stderr; + gOutFile = stdout; + + argc--; + argv++; + + rt = JS_NewRuntime(64L * 1024L * 1024L); + if (!rt) + return 1; + JS_SetContextCallback(rt, ContextCallback); + + cx = JS_NewContext(rt, gStackChunkSize); + if (!cx) + return 1; + +#ifdef JS_THREADSAFE + JS_BeginRequest(cx); +#endif + + glob = JS_NewObject(cx, &global_class, NULL, NULL); + if (!glob) + return 1; +#ifdef LAZY_STANDARD_CLASSES + JS_SetGlobalObject(cx, glob); +#else + if (!JS_InitStandardClasses(cx, glob)) + return 1; +#endif + if (!JS_DefineFunctions(cx, glob, shell_functions)) + return 1; + + it = JS_DefineObject(cx, glob, "it", &its_class, NULL, 0); + if (!it) + return 1; + if (!JS_DefineProperties(cx, it, its_props)) + return 1; + if (!JS_DefineFunctions(cx, it, its_methods)) + 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); +#ifdef LIVECONNECT + if (java_env) + (*java_env)->GetJavaVM(java_env, &java_vm); +#endif + /* + * 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 LIVECONNECT + if (!JSJ_SimpleInit(cx, glob, java_vm, getenv("CLASSPATH"))) + return 1; +#endif + + envobj = JS_DefineObject(cx, glob, "environment", &env_class, NULL, 0); + if (!envobj || !JS_SetPrivate(cx, envobj, envp)) + return 1; + +#ifdef NARCISSUS + { + jsval v; + static const char Object_prototype[] = "Object.prototype"; + + if (!JS_DefineFunction(cx, glob, "snarf", snarf, 1, 0)) + return 1; + if (!JS_DefineFunction(cx, glob, "evaluate", Evaluate, 3, 0)) + return 1; + + if (!JS_EvaluateScript(cx, glob, + Object_prototype, sizeof Object_prototype - 1, + NULL, 0, &v)) { + return 1; + } + if (!JS_DefineFunction(cx, JSVAL_TO_OBJECT(v), "__defineProperty__", + defineProperty, 5, 0)) { + return 1; + } + } +#endif + + result = ProcessArgs(cx, glob, argv, argc); + +#ifdef JSDEBUGGER + if (jsdc) { +#ifdef JSDEBUGGER_C_UI + if (jsdbc) + JSDB_TermDebugger(jsdc); +#endif /* JSDEBUGGER_C_UI */ + JSD_DebuggerOff(jsdc); + } +#endif /* JSDEBUGGER */ + +#ifdef JS_THREADSAFE + JS_EndRequest(cx); +#endif + + JS_DestroyContext(cx); + JS_DestroyRuntime(rt); + JS_ShutDown(); + return result; +} |