/* -*- 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 #include #include #include #include #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 #include #include #endif #if defined(XP_WIN) || defined(XP_OS2) #include /* 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 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("", 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 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 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 , 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 #include /* * 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; }