/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** 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 "jstypes.h" #include "jsarena.h" #include "jsutil.h" #include "jsprf.h" #include "jsapi.h" #include "jsatom.h" #include "jscntxt.h" #include "jsdbgapi.h" #include "jsemit.h" #include "jsfun.h" #include "jsgc.h" #include "jslock.h" #include "jsobj.h" #include "jsparse.h" #include "jsscope.h" #include "jsscript.h" #ifdef PERLCONNECT #include "perlconnect/jsperl.h" #endif #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 #define EXITCODE_RUNTIME_ERROR 3 #define EXITCODE_FILE_NOT_FOUND 4 size_t gStackChunkSize = 8192; static size_t gMaxStackSize = 0; static jsuword gStackBase; int gExitCode = 0; JSBool gQuitting = JS_FALSE; FILE *gErrFile = NULL; FILE *gOutFile = NULL; #ifdef XP_MAC #if defined(MAC_TEST_HACK) || defined(XP_MAC_MPW) /* this is the data file that all Print strings will be echoed into */ FILE *gTestResultFile = NULL; #define isatty(f) 0 #else #define isatty(f) 1 #endif char *strdup(const char *str) { char *copy = (char *) malloc(strlen(str)+1); if (copy) strcpy(copy, str); return copy; } #ifdef XP_MAC_MPW /* Macintosh MPW replacements for the ANSI routines. These translate LF's to CR's because the MPW libraries supplied by Metrowerks don't do that for some reason. */ static void translateLFtoCR(char *str, int length) { char *limit = str + length; while (str != limit) { if (*str == '\n') *str = '\r'; str++; } } int fputc(int c, FILE *file) { char buffer = c; if (buffer == '\n') buffer = '\r'; return fwrite(&buffer, 1, 1, file); } int fputs(const char *s, FILE *file) { char buffer[4096]; int n = strlen(s); int extra = 0; while (n > sizeof buffer) { memcpy(buffer, s, sizeof buffer); translateLFtoCR(buffer, sizeof buffer); extra += fwrite(buffer, 1, sizeof buffer, file); n -= sizeof buffer; s += sizeof buffer; } memcpy(buffer, s, n); translateLFtoCR(buffer, n); return extra + fwrite(buffer, 1, n, file); } int fprintf(FILE* file, const char *format, ...) { va_list args; char smallBuffer[4096]; int n; int bufferSize = sizeof smallBuffer; char *buffer = smallBuffer; int result; va_start(args, format); n = vsnprintf(buffer, bufferSize, format, args); va_end(args); while (n < 0) { if (buffer != smallBuffer) free(buffer); bufferSize <<= 1; buffer = malloc(bufferSize); if (!buffer) { JS_ASSERT(JS_FALSE); return 0; } va_start(args, format); n = vsnprintf(buffer, bufferSize, format, args); va_end(args); } translateLFtoCR(buffer, n); result = fwrite(buffer, 1, n, file); if (buffer != smallBuffer) free(buffer); return result; } #else #include #include static char* mac_argv[] = { "js", NULL }; static void initConsole(StringPtr consoleName, const char* startupMessage, int *argc, char** *argv) { SIOUXSettings.autocloseonquit = true; SIOUXSettings.asktosaveonclose = false; /* SIOUXSettings.initializeTB = false; SIOUXSettings.showstatusline = true;*/ puts(startupMessage); SIOUXSetTitle(consoleName); /* set up a buffer for stderr (otherwise it's a pig). */ setvbuf(stderr, (char *) malloc(BUFSIZ), _IOLBF, BUFSIZ); *argc = 1; *argv = mac_argv; } #ifdef LIVECONNECT /* Little hack to provide a default CLASSPATH on the Mac. */ #define getenv(var) mac_getenv(var) static char* mac_getenv(const char* var) { if (strcmp(var, "CLASSPATH") == 0) { static char class_path[] = "liveconnect.jar"; return class_path; } return NULL; } #endif /* LIVECONNECT */ #endif #endif #ifdef JSDEBUGGER static JSDContext *_jsdc; #ifdef JSDEBUGGER_JAVA_UI static JSDJContext *_jsdjc; #endif /* JSDEBUGGER_JAVA_UI */ #endif /* JSDEBUGGER */ static JSBool reportWarnings = JS_TRUE; 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); #ifdef EDITLINE extern char *readline(const char *prompt); extern void add_history(char *line); #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); #ifdef XP_MAC_MPW /* Print a CR after the prompt because MPW grabs the entire line when entering an interactive command */ fputc('\n', gOutFile); #endif if (!fgets(line, sizeof line, file)) return JS_FALSE; strcpy(bufp, line); } return JS_TRUE; } static void Process(JSContext *cx, JSObject *obj, char *filename) { JSBool ok, hitEOF; JSScript *script; jsval result; JSString *str; char buffer[4096]; char *bufp; int lineno; int startline; FILE *file; jsuword stackLimit; if (!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; } } 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); if (!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); script = JS_CompileFileHandle(cx, obj, filename, file); if (script) { (void)JS_ExecuteScript(cx, obj, script, &result); JS_DestroyScript(cx, script); } 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), #ifdef JSDEBUGGER "typein", #else NULL, #endif startline); if (script) { ok = JS_ExecuteScript(cx, obj, script, &result); if (ok && result != JSVAL_VOID) { 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"); return; } static int usage(void) { fprintf(gErrFile, "%s\n", JS_GetImplementationVersion()); fprintf(gErrFile, "usage: js [-PswW] [-b branchlimit] [-c stackchunksize] [-v version] [-f scriptfile] [-S maxstacksize] [scriptfile] [scriptarg...]\n"); return 2; } static uint32 gBranchCount; static uint32 gBranchLimit; static JSBool my_BranchCallback(JSContext *cx, JSScript *script) { if (++gBranchCount == gBranchLimit) { if (script->filename) fprintf(gErrFile, "%s:", script->filename); fprintf(gErrFile, "%u: script branches too much (%u callbacks)\n", script->lineno, gBranchLimit); gBranchCount = 0; return JS_FALSE; } if ((gBranchCount & 0x3fff) == 1) JS_MaybeGC(cx); return JS_TRUE; } 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; /* * 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 'v': case 'S': ++i; break; } } /* * 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; case 'w': reportWarnings = JS_TRUE; break; case 'W': reportWarnings = JS_FALSE; break; case 's': JS_ToggleOptions(cx, JSOPTION_STRICT); 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]); JS_SetBranchCallback(cx, my_BranchCallback); break; case 'c': /* set stack chunk size */ gStackChunkSize = atoi(argv[++i]); break; case 'f': if (++i == argc) { return usage(); } Process(cx, obj, argv[i]); /* * XXX: js -f foo.js should interpret foo.js and then * drop into interactive mode, but that breaks test * harness. Just execute foo.js for now. */ isInteractive = JS_FALSE; break; case 'S': if (++i == argc) { return usage(); } /* Set maximum stack size. */ gMaxStackSize = atoi(argv[i]); break; default: return usage(); } } if (filename || isInteractive) Process(cx, obj, filename); 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 struct { const char *name; uint32 flag; } js_options[] = { {"strict", JSOPTION_STRICT}, {"werror", JSOPTION_WERROR}, {"atline", JSOPTION_ATLINE}, {0, 0} }; 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 void my_LoadErrorReporter(JSContext *cx, const char *message, JSErrorReport *report); static void my_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report); static JSBool Load(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { uintN i; JSString *str; const char *filename; JSScript *script; JSBool ok; jsval result; JSErrorReporter older; 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; older = JS_SetErrorReporter(cx, my_LoadErrorReporter); oldopts = JS_GetOptions(cx); JS_SetOptions(cx, oldopts | JSOPTION_COMPILE_N_GO); script = JS_CompileFile(cx, obj, filename); if (!script) { ok = JS_FALSE; } else { ok = JS_ExecuteScript(cx, obj, script, &result); JS_DestroyScript(cx, script); } JS_SetOptions(cx, oldopts); JS_SetErrorReporter(cx, older); if (!ok) return JS_FALSE; } return JS_TRUE; } static JSBool Print(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { uintN i, n; JSString *str; for (i = n = 0; i < argc; i++) { str = JS_ValueToString(cx, argv[i]); if (!str) return JS_FALSE; fprintf(gOutFile, "%s%s", i ? " " : "", JS_GetStringBytes(str)); } n++; if (n) fputc('\n', gOutFile); 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; } #ifdef GC_MARK_DEBUG extern JS_FRIEND_DATA(FILE *) js_DumpGCHeap; #endif static JSBool GC(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSRuntime *rt; uint32 preBytes; rt = cx->runtime; preBytes = rt->gcBytes; #ifdef GC_MARK_DEBUG if (argc && JSVAL_IS_STRING(argv[0])) { char *name = JS_GetStringBytes(JSVAL_TO_STRING(argv[0])); FILE *file = fopen(name, "w"); if (!file) { fprintf(gErrFile, "gc: can't open %s: %s\n", strerror(errno)); return JS_FALSE; } js_DumpGCHeap = file; } else { js_DumpGCHeap = stdout; } #endif JS_GC(cx); #ifdef GC_MARK_DEBUG if (js_DumpGCHeap != stdout) fclose(js_DumpGCHeap); js_DumpGCHeap = NULL; #endif 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 return JS_TRUE; } static JSScript * ValueToScript(JSContext *cx, jsval v) { JSScript *script; JSFunction *fun; if (JSVAL_IS_OBJECT(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); } return script; } static JSBool GetTrapArgs(JSContext *cx, uintN argc, jsval *argv, JSScript **scriptp, int32 *ip) { uintN intarg; JSScript *script; *scriptp = cx->fp->down->script; *ip = 0; if (argc != 0) { intarg = 0; if (JS_TypeOfValue(cx, argv[0]) == JSTYPE_FUNCTION) { script = ValueToScript(cx, argv[0]); 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 (*rval != JSVAL_VOID) 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 SrcNotes(JSContext *cx, JSScript *script) { uintN offset, delta, caseOff; jssrcnote *notes, *sn; JSSrcNoteType type; jsatomid atomIndex; JSAtom *atom; fprintf(gOutFile, "\nSource notes:\n"); offset = 0; notes = SCRIPT_NOTES(script); for (sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { delta = SN_DELTA(sn); offset += delta; fprintf(gOutFile, "%3u: %5u [%4u] %-8s", PTRDIFF(sn, notes, jssrcnote), offset, delta, js_SrcNoteSpec[SN_TYPE(sn)].name); type = (JSSrcNoteType) SN_TYPE(sn); 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_COND: case SRC_IF_ELSE: case SRC_WHILE: case SRC_PCBASE: case SRC_PCDELTA: fprintf(gOutFile, " offset %u", (uintN) js_GetSrcNoteOffset(sn, 0)); break; case SRC_LABEL: case SRC_LABELBRACE: case SRC_BREAK2LABEL: case SRC_CONT2LABEL: case SRC_FUNCDEF: { const char *bytes; JSFunction *fun; JSString *str; atomIndex = (jsatomid) js_GetSrcNoteOffset(sn, 0); atom = js_GetAtom(cx, &script->atomMap, atomIndex); if (type != SRC_FUNCDEF) { bytes = js_AtomToPrintableString(cx, atom); } else { fun = (JSFunction *) JS_GetPrivate(cx, ATOM_TO_OBJECT(atom)); str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT); bytes = str ? JS_GetStringBytes(str) : "N/A"; } fprintf(gOutFile, " atom %u (%s)", (uintN)atomIndex, 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); break; case SRC_CATCH: delta = (uintN) js_GetSrcNoteOffset(sn, 0); if (delta) fprintf(gOutFile, " guard size %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; } static JSBool TryNotes(JSContext *cx, JSScript *script) { JSTryNote *tn = script->trynotes; if (!tn) return JS_TRUE; fprintf(gOutFile, "\nException table:\nstart\tend\tcatch\n"); while (tn->start && tn->catchStart) { fprintf(gOutFile, " %d\t%d\t%d\n", tn->start, tn->start + tn->length, tn->catchStart); tn++; } 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) continue; if (JSVAL_IS_FUNCTION(cx, argv[i])) { JSFunction *fun = JS_ValueToFunction(cx, argv[i]); if (fun && (fun->flags & JSFUN_FLAGS_MASK)) { uint8 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); #undef SHOW_FLAG putchar('\n'); } } js_Disassemble(cx, script, lines, stdout); SrcNotes(cx, script); TryNotes(cx, script); } return JS_TRUE; } 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; static char sep[] = ";-------------------------"; for (i = 0; i < argc; i++) { script = ValueToScript(cx, argv[i]); if (!script) continue; if (!script || !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); 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) return JS_FALSE; pc += len; } bail: fclose(file); } return JS_TRUE; #undef LINE_BUF_LEN } static JSBool Tracing(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSBool bval; JSString *str; 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; fprintf(gErrFile, "tracing: illegal argument %s\n", JS_GetStringBytes(str)); return JS_TRUE; } cx->tracefp = bval ? stderr : NULL; return JS_TRUE; } typedef struct DumpAtomArgs { JSContext *cx; FILE *fp; } DumpAtomArgs; static int DumpAtom(JSHashEntry *he, int i, void *arg) { DumpAtomArgs *args = (DumpAtomArgs *)arg; FILE *fp = args->fp; JSAtom *atom = (JSAtom *)he; fprintf(fp, "%3d %08x %5lu ", i, (uintN)he->keyHash, (unsigned long)atom->number); if (ATOM_IS_STRING(atom)) fprintf(fp, "\"%s\"\n", js_AtomToPrintableString(args->cx, atom)); else if (ATOM_IS_INT(atom)) fprintf(fp, "%ld\n", (long)ATOM_TO_INT(atom)); else fprintf(fp, "%.16g\n", *ATOM_TO_DOUBLE(atom)); return HT_ENUMERATE_NEXT; } static void DumpScope(JSContext *cx, JSObject *obj, FILE *fp) { uintN i; JSScope *scope; JSScopeProperty *sprop; 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, sprop); if (JSVAL_IS_INT(sprop->id)) { fprintf(fp, " [%ld]", (long)JSVAL_TO_INT(sprop->id)); } else { fprintf(fp, " \"%s\"", js_AtomToPrintableString(cx, (JSAtom *)sprop->id)); } #define DUMP_ATTR(name) if (sprop->attrs & JSPROP_##name) fputs(" " #name, fp) DUMP_ATTR(ENUMERATE); DUMP_ATTR(READONLY); DUMP_ATTR(PERMANENT); DUMP_ATTR(EXPORTED); DUMP_ATTR(GETTER); DUMP_ATTR(SETTER); #undef DUMP_ATTR fprintf(fp, " slot %lu flags %x shortid %d\n", 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; JSAtom *atom; JSObject *obj2; JSProperty *prop; jsval value; for (i = 0; i < argc; i++) { str = JS_ValueToString(cx, argv[i]); if (!str) return JS_FALSE; bytes = JS_GetStringBytes(str); if (strcmp(bytes, "arena") == 0) { #ifdef JS_ARENAMETER JS_DumpArenaStats(stdout); #endif } else if (strcmp(bytes, "atom") == 0) { DumpAtomArgs args; fprintf(gOutFile, "\natom table contents:\n"); args.cx = cx; args.fp = stdout; JS_HashTableEnumerateEntries(cx->runtime->atomState.table, DumpAtom, &args); #ifdef HASHMETER JS_HashTableDumpMeter(cx->runtime->atomState.table, DumpAtom, stdout); #endif } else if (strcmp(bytes, "global") == 0) { DumpScope(cx, cx->globalObject, stdout); } else { atom = js_Atomize(cx, bytes, JS_GetStringLength(str), 0); if (!atom) return JS_FALSE; if (!js_FindProperty(cx, (jsid)atom, &obj, &obj2, &prop)) return JS_FALSE; if (prop) { OBJ_DROP_PROPERTY(cx, obj2, prop); if (!OBJ_GET_PROPERTY(cx, obj, (jsid)atom, &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; } #endif /* DEBUG */ #ifdef TEST_EXPORT static JSBool DoExport(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSAtom *atom; JSObject *obj2; JSProperty *prop; JSBool ok; uintN attrs; if (argc != 2) { JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_DOEXP_USAGE); return JS_FALSE; } if (!JS_ValueToObject(cx, argv[0], &obj)) return JS_FALSE; argv[0] = OBJECT_TO_JSVAL(obj); atom = js_ValueToStringAtom(cx, argv[1]); if (!atom) return JS_FALSE; if (!OBJ_LOOKUP_PROPERTY(cx, obj, (jsid)atom, &obj2, &prop)) return JS_FALSE; if (!prop) { ok = OBJ_DEFINE_PROPERTY(cx, obj, id, JSVAL_VOID, NULL, NULL, JSPROP_EXPORTED, NULL); } else { ok = OBJ_GET_ATTRIBUTES(cx, obj, (jsid)atom, prop, &attrs); if (ok) { attrs |= JSPROP_EXPORTED; ok = OBJ_SET_ATTRIBUTES(cx, obj, (jsid)atom, prop, &attrs); } OBJ_DROP_PROPERTY(cx, obj2, prop); } return ok; } #endif #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, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { fprintf(gOutFile, "built on %s at %s\n", __DATE__, __TIME__); 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, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSString *str; str = JS_ValueToString(cx, argv[0]); if (!str) return JS_FALSE; if (!JS_InternUCStringN(cx, JS_GetStringChars(str), JS_GetStringLength(str))) { return JS_FALSE; } 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 JSFunctionSpec shell_functions[] = { {"version", Version, 0}, {"options", Options, 0}, {"load", Load, 1}, {"print", Print, 0}, {"help", Help, 0}, {"quit", Quit, 0}, {"gc", GC, 0}, {"trap", Trap, 3}, {"untrap", Untrap, 2}, {"line2pc", LineToPC, 0}, {"pc2line", PCToLine, 0}, #ifdef DEBUG {"dis", Disassemble, 1}, {"dissrc", DisassWithSrc, 1}, {"notes", Notes, 1}, {"tracing", Tracing, 0}, {"stats", DumpStats, 1}, #endif #ifdef TEST_EXPORT {"xport", DoExport, 2}, #endif #ifdef TEST_CVTARGS {"cvtargs", ConvertArgs, 0, 0, 12}, #endif {"build", BuildDate, 0}, {"clear", Clear, 0}, {"intern", Intern, 1}, {"clone", Clone, 1}, {"seal", Seal, 1, 0, 1}, {0} }; /* NOTE: These must be kept in sync with the above. */ static char *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", "print([exp ...]) Evaluate and print expressions", "help([name ...]) Display usage and help messages", "quit() Quit the shell", "gc() Run the garbage collector", "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", #ifdef DEBUG "dis([fun]) Disassemble functions into bytecodes", "dissrc([fun]) Disassemble functions with source lines", "notes([fun]) Show source notes for functions", "tracing([toggle]) Turn tracing on or off", "stats([string ...]) Dump 'arena', 'atom', 'global' stats", #endif #ifdef TEST_EXPORT "xport(obj, id) Export identified property from object", #endif #ifdef TEST_CVTARGS "cvtargs(b, c, ...) Test JS_ConvertArguments", #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", 0 }; static void ShowHelpHeader(void) { fprintf(gOutFile, "%-9s %-22s %s\n", "Command", "Usage", "Description"); fprintf(gOutFile, "%-9s %-22s %s\n", "=======", "=====", "==========="); } static void ShowHelpForCommand(uintN n) { fprintf(gOutFile, "%-9.9s %s\n", shell_functions[n].name, shell_help_messages[n]); } 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) { ShowHelpHeader(); for (i = 0; shell_functions[i].name; i++) ShowHelpForCommand(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; ShowHelpHeader(); } did_something = 1; ShowHelpForCommand(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; } /* * 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}, {"height", ITS_HEIGHT, JSPROP_ENUMERATE}, {"width", ITS_WIDTH, JSPROP_ENUMERATE}, {"funny", ITS_FUNNY, JSPROP_ENUMERATE}, {"array", ITS_ARRAY, JSPROP_ENUMERATE}, {"rdonly", ITS_RDONLY, JSPROP_READONLY}, {0} }; 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 JSFunctionSpec its_methods[] = { {"item", its_item, 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_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) { 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) && !strcmp(JS_GetStringBytes(JSVAL_TO_STRING(id)), "noisy")) { return JS_ValueToBoolean(cx, *vp, &its_noisy); } return JS_TRUE; } static JSBool its_enumerate(JSContext *cx, JSObject *obj) { if (its_noisy) fprintf(gOutFile, "enumerate its properties\n"); 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, its_addProperty, its_delProperty, its_getProperty, its_setProperty, its_enumerate, (JSResolveOp)its_resolve, its_convert, its_finalize }; JSErrorFormatString jsShell_ErrorFormatString[JSErr_Limit] = { #if JS_HAS_DFLT_MSG_STRINGS #define MSG_DEF(name, number, count, exception, format) \ { format, count } , #else #define MSG_DEF(name, number, count, exception, format) \ { NULL, count } , #endif #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_LoadErrorReporter(JSContext *cx, const char *message, JSErrorReport *report) { if (!report) { fprintf(gErrFile, "%s\n", message); return; } /* Ignore any exceptions */ if (JSREPORT_IS_EXCEPTION(report->flags)) return; /* Otherwise, fall back to the ordinary error reporter. */ my_ErrorReporter(cx, message, report); } 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)) 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 #define LAZY_STANDARD_CLASSES 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, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, global_enumerate, (JSResolveOp) global_resolve, JS_ConvertStub, JS_FinalizeStub }; 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 }; #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); } #include #include static JSBool snarf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSString *str; const char *filename; int fd, cc; JSBool ok; size_t len; char *buf; struct stat sb; str = JS_ValueToString(cx, argv[0]); if (!str) return JS_FALSE; filename = JS_GetStringBytes(str); fd = open(filename, O_RDONLY); ok = JS_TRUE; len = 0; buf = NULL; if (fd < 0) { JS_ReportError(cx, "can't open %s: %s", filename, strerror(errno)); ok = JS_FALSE; } else if (fstat(fd, &sb) < 0) { JS_ReportError(cx, "can't stat %s", filename); ok = JS_FALSE; } else { len = sb.st_size; buf = JS_malloc(cx, len + 1); if (!buf) { ok = JS_FALSE; } else if ((cc = read(fd, buf, len)) != len) { JS_free(cx, buf); JS_ReportError(cx, "can't read %s: %s", filename, (cc < 0) ? strerror(errno) : "short read"); ok = JS_FALSE; } } close(fd); if (!ok) 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 */ int main(int argc, char **argv, char **envp) { int stackDummy; JSVersion version; JSRuntime *rt; JSContext *cx; JSObject *glob, *it, *envobj; int result; #ifdef LIVECONNECT JavaVM *java_vm = NULL; #endif #ifdef JSDEBUGGER_JAVA_UI JNIEnv *java_env; #endif 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; #ifdef XP_MAC #ifndef XP_MAC_MPW initConsole("\pJavaScript Shell", "Welcome to js shell.", &argc, &argv); #endif #endif #ifdef MAC_TEST_HACK /* Open a file "testArgs.txt" and read each line into argc/argv. Re-direct all output to "results.txt" */ { char argText[256]; FILE *f = fopen("testargs.txt", "r"); if (f) { int maxArgs = 32; /* arbitrary max !!! */ int argText_strlen; argc = 1; argv = malloc(sizeof(char *) * maxArgs); argv[0] = NULL; while (fgets(argText, 255, f)) { /* argText includes '\n' */ argText_strlen = strlen(argText); argv[argc] = malloc(argText_strlen); strncpy(argv[argc], argText, argText_strlen - 1); argv[argc][argText_strlen - 1] = '\0'; argc++; if (argc >= maxArgs) break; } fclose(f); } gTestResultFile = fopen("results.txt", "w"); } gErrFile = gTestResultFile; gOutFile = gTestResultFile; #endif version = JSVERSION_DEFAULT; argc--; argv++; rt = JS_NewRuntime(64L * 1024L * 1024L); if (!rt) return 1; cx = JS_NewContext(rt, gStackChunkSize); if (!cx) return 1; JS_SetErrorReporter(cx, my_ErrorReporter); 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; /* Set version only after there is a global object. */ if (version != JSVERSION_DEFAULT) JS_SetVersion(cx, version); 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 PERLCONNECT if (!JS_InitPerlClass(cx, glob)) return 1; #endif #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 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_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) JSD_DebuggerOff(_jsdc); #endif /* JSDEBUGGER */ #ifdef MAC_TEST_HACK fclose(gTestResultFile); #endif JS_DestroyContext(cx); JS_DestroyRuntime(rt); JS_ShutDown(); return result; }