diff options
Diffstat (limited to 'src/extension/script/js/jscntxt.c')
| -rw-r--r-- | src/extension/script/js/jscntxt.c | 702 |
1 files changed, 702 insertions, 0 deletions
diff --git a/src/extension/script/js/jscntxt.c b/src/extension/script/js/jscntxt.c new file mode 100644 index 000000000..85496b89b --- /dev/null +++ b/src/extension/script/js/jscntxt.c @@ -0,0 +1,702 @@ +/* -*- 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 execution context. + */ +#include "jsstddef.h" +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include "jstypes.h" +#include "jsarena.h" /* Added by JSIFY */ +#include "jsutil.h" /* Added by JSIFY */ +#include "jsclist.h" +#include "jsprf.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsdbgapi.h" +#include "jsexn.h" +#include "jsgc.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsscan.h" +#include "jsscript.h" +#include "jsstr.h" + +JSContext * +js_NewContext(JSRuntime *rt, size_t stackChunkSize) +{ + JSContext *cx; + JSBool ok, first; + + cx = (JSContext *) malloc(sizeof *cx); + if (!cx) + return NULL; + memset(cx, 0, sizeof *cx); + + cx->runtime = rt; +#if JS_STACK_GROWTH_DIRECTION > 0 + cx->stackLimit = (jsuword)-1; +#endif +#ifdef JS_THREADSAFE + js_InitContextForLocking(cx); +#endif + + JS_LOCK_GC(rt); + for (;;) { + first = (rt->contextList.next == &rt->contextList); + if (rt->state == JSRTS_UP) { + JS_ASSERT(!first); + break; + } + if (rt->state == JSRTS_DOWN) { + JS_ASSERT(first); + rt->state = JSRTS_LAUNCHING; + break; + } + JS_WAIT_CONDVAR(rt->stateChange, JS_NO_TIMEOUT); + } + JS_APPEND_LINK(&cx->links, &rt->contextList); + JS_UNLOCK_GC(rt); + + /* + * First we do the infallible, every-time per-context initializations. + * Should a later, fallible initialization (js_InitRegExpStatics, e.g., + * or the stuff under 'if (first)' below) fail, at least the version + * and arena-pools will be valid and safe to use (say, from the last GC + * done by js_DestroyContext). + */ + cx->version = JSVERSION_DEFAULT; + cx->jsop_eq = JSOP_EQ; + cx->jsop_ne = JSOP_NE; + JS_InitArenaPool(&cx->stackPool, "stack", stackChunkSize, sizeof(jsval)); + JS_InitArenaPool(&cx->tempPool, "temp", 1024, sizeof(jsdouble)); + +#if JS_HAS_REGEXPS + if (!js_InitRegExpStatics(cx, &cx->regExpStatics)) { + js_DestroyContext(cx, JS_NO_GC); + return NULL; + } +#endif +#if JS_HAS_EXCEPTIONS + cx->throwing = JS_FALSE; +#endif + + /* + * If cx is the first context on this runtime, initialize well-known atoms, + * keywords, numbers, and strings. If one of these steps should fail, the + * runtime will be left in a partially initialized state, with zeroes and + * nulls stored in the default-initialized remainder of the struct. We'll + * clean the runtime up under js_DestroyContext, because cx will be "last" + * as well as "first". + */ + if (first) { + ok = (rt->atomState.liveAtoms == 0) + ? js_InitAtomState(cx, &rt->atomState) + : js_InitPinnedAtoms(cx, &rt->atomState); + if (ok) + ok = js_InitScanner(cx); + if (ok) + ok = js_InitRuntimeNumberState(cx); + if (ok) + ok = js_InitRuntimeStringState(cx); + if (!ok) { + js_DestroyContext(cx, JS_NO_GC); + return NULL; + } + + JS_LOCK_GC(rt); + rt->state = JSRTS_UP; + JS_NOTIFY_ALL_CONDVAR(rt->stateChange); + JS_UNLOCK_GC(rt); + } + + return cx; +} + +void +js_DestroyContext(JSContext *cx, JSGCMode gcmode) +{ + JSRuntime *rt; + JSBool last; + JSArgumentFormatMap *map; + + rt = cx->runtime; + + /* Remove cx from context list first. */ + JS_LOCK_GC(rt); + JS_ASSERT(rt->state == JSRTS_UP || rt->state == JSRTS_LAUNCHING); + JS_REMOVE_LINK(&cx->links); + last = (rt->contextList.next == &rt->contextList); + if (last) + rt->state = JSRTS_LANDING; + JS_UNLOCK_GC(rt); + + if (last) { +#ifdef JS_THREADSAFE + /* + * If cx is not in a request already, begin one now so that we wait + * for any racing GC started on a not-last context to finish, before + * we plow ahead and unpin atoms. Note that even though we begin a + * request here if necessary, we end all requests on cx below before + * forcing a final GC. This lets any not-last context destruction + * racing in another thread try to force or maybe run the GC, but by + * that point, rt->state will not be JSRTS_UP, and that GC attempt + * will return early. + */ + if (cx->requestDepth == 0) + JS_BeginRequest(cx); +#endif + + /* Unpin all pinned atoms before final GC. */ + js_UnpinPinnedAtoms(&rt->atomState); + + /* Unlock and clear GC things held by runtime pointers. */ + js_FinishRuntimeNumberState(cx); + js_FinishRuntimeStringState(cx); + + /* Clear debugging state to remove GC roots. */ + JS_ClearAllTraps(cx); + JS_ClearAllWatchPoints(cx); + } + +#if JS_HAS_REGEXPS + /* + * Remove more GC roots in regExpStatics, then collect garbage. + * XXX anti-modularity alert: we rely on the call to js_RemoveRoot within + * XXX this function call to wait for any racing GC to complete, in the + * XXX case where JS_DestroyContext is called outside of a request on cx + */ + js_FreeRegExpStatics(cx, &cx->regExpStatics); +#endif + +#ifdef JS_THREADSAFE + /* + * Destroying a context implicitly calls JS_EndRequest(). Also, we must + * end our request here in case we are "last" -- in that event, another + * js_DestroyContext that was not last might be waiting in the GC for our + * request to end. We'll let it run below, just before we do the truly + * final GC and then free atom state. + * + * At this point, cx must be inaccessible to other threads. It's off the + * rt->contextList, and it should not be reachable via any object private + * data structure. + */ + while (cx->requestDepth != 0) + JS_EndRequest(cx); +#endif + + if (last) { + /* Always force, so we wait for any racing GC to finish. */ + js_ForceGC(cx, GC_LAST_CONTEXT); + + /* Iterate until no finalizer removes a GC root or lock. */ + while (rt->gcPoke) + js_GC(cx, GC_LAST_CONTEXT); + + /* Try to free atom state, now that no unrooted scripts survive. */ + if (rt->atomState.liveAtoms == 0) + js_FreeAtomState(cx, &rt->atomState); + + /* Take the runtime down, now that it has no contexts or atoms. */ + JS_LOCK_GC(rt); + rt->state = JSRTS_DOWN; + JS_NOTIFY_ALL_CONDVAR(rt->stateChange); + JS_UNLOCK_GC(rt); + } else { + if (gcmode == JS_FORCE_GC) + js_ForceGC(cx, 0); + else if (gcmode == JS_MAYBE_GC) + JS_MaybeGC(cx); + } + + /* Free the stuff hanging off of cx. */ + JS_FinishArenaPool(&cx->stackPool); + JS_FinishArenaPool(&cx->tempPool); + if (cx->lastMessage) + free(cx->lastMessage); + + /* Remove any argument formatters. */ + map = cx->argumentFormatMap; + while (map) { + JSArgumentFormatMap *temp = map; + map = map->next; + JS_free(cx, temp); + } + + /* Destroy the resolve recursion damper. */ + if (cx->resolvingTable) { + JS_DHashTableDestroy(cx->resolvingTable); + cx->resolvingTable = NULL; + } + + /* Finally, free cx itself. */ + free(cx); +} + +JSBool +js_ValidContextPointer(JSRuntime *rt, JSContext *cx) +{ + JSCList *cl; + + for (cl = rt->contextList.next; cl != &rt->contextList; cl = cl->next) { + if (cl == &cx->links) + return JS_TRUE; + } + JS_RUNTIME_METER(rt, deadContexts); + return JS_FALSE; +} + +JSContext * +js_ContextIterator(JSRuntime *rt, JSBool unlocked, JSContext **iterp) +{ + JSContext *cx = *iterp; + + if (unlocked) + JS_LOCK_GC(rt); + if (!cx) + cx = (JSContext *)&rt->contextList; + cx = (JSContext *)cx->links.next; + if (&cx->links == &rt->contextList) + cx = NULL; + *iterp = cx; + if (unlocked) + JS_UNLOCK_GC(rt); + return cx; +} + +static void +ReportError(JSContext *cx, const char *message, JSErrorReport *reportp) +{ + /* + * Check the error report, and set a JavaScript-catchable exception + * if the error is defined to have an associated exception. If an + * exception is thrown, then the JSREPORT_EXCEPTION flag will be set + * on the error report, and exception-aware hosts should ignore it. + */ + if (reportp && reportp->errorNumber == JSMSG_UNCAUGHT_EXCEPTION) + reportp->flags |= JSREPORT_EXCEPTION; + +#if JS_HAS_ERROR_EXCEPTIONS + /* + * Call the error reporter only if an exception wasn't raised. + * + * If an exception was raised, then we call the debugErrorHook + * (if present) to give it a chance to see the error before it + * propagates out of scope. This is needed for compatability + * with the old scheme. + */ + if (!js_ErrorToException(cx, message, reportp)) { + js_ReportErrorAgain(cx, message, reportp); + } else if (cx->runtime->debugErrorHook && cx->errorReporter) { + JSDebugErrorHook hook = cx->runtime->debugErrorHook; + /* test local in case debugErrorHook changed on another thread */ + if (hook) + hook(cx, message, reportp, cx->runtime->debugErrorHookData); + } +#else + js_ReportErrorAgain(cx, message, reportp); +#endif +} + +/* + * We don't post an exception in this case, since doing so runs into + * complications of pre-allocating an exception object which required + * running the Exception class initializer early etc. + * Instead we just invoke the errorReporter with an "Out Of Memory" + * type message, and then hope the process ends swiftly. + */ +void +js_ReportOutOfMemory(JSContext *cx, JSErrorCallback callback) +{ + JSStackFrame *fp; + JSErrorReport report; + JSErrorReporter onError = cx->errorReporter; + + /* Get the message for this error, but we won't expand any arguments. */ + const JSErrorFormatString *efs = callback(NULL, NULL, JSMSG_OUT_OF_MEMORY); + const char *msg = efs ? efs->format : "Out of memory"; + + /* Fill out the report, but don't do anything that requires allocation. */ + memset(&report, 0, sizeof (struct JSErrorReport)); + report.flags = JSREPORT_ERROR; + report.errorNumber = JSMSG_OUT_OF_MEMORY; + + /* + * Walk stack until we find a frame that is associated with some script + * rather than a native frame. + */ + for (fp = cx->fp; fp; fp = fp->down) { + if (fp->script && fp->pc) { + report.filename = fp->script->filename; + report.lineno = js_PCToLineNumber(cx, fp->script, fp->pc); + break; + } + } + + /* + * If debugErrorHook is present then we give it a chance to veto + * sending the error on to the regular ErrorReporter. + */ + if (onError) { + JSDebugErrorHook hook = cx->runtime->debugErrorHook; + if (hook && + !hook(cx, msg, &report, cx->runtime->debugErrorHookData)) { + onError = NULL; + } + } + + if (onError) + onError(cx, msg, &report); +} + +JSBool +js_ReportErrorVA(JSContext *cx, uintN flags, const char *format, va_list ap) +{ + char *last; + JSStackFrame *fp; + JSErrorReport report; + JSBool warning; + + if ((flags & JSREPORT_STRICT) && !JS_HAS_STRICT_OPTION(cx)) + return JS_TRUE; + + last = JS_vsmprintf(format, ap); + if (!last) + return JS_FALSE; + + memset(&report, 0, sizeof (struct JSErrorReport)); + report.flags = flags; + + /* Find the top-most active script frame, for best line number blame. */ + for (fp = cx->fp; fp; fp = fp->down) { + if (fp->script && fp->pc) { + report.filename = fp->script->filename; + report.lineno = js_PCToLineNumber(cx, fp->script, fp->pc); + break; + } + } + + warning = JSREPORT_IS_WARNING(report.flags); + if (warning && JS_HAS_WERROR_OPTION(cx)) { + report.flags &= ~JSREPORT_WARNING; + warning = JS_FALSE; + } + + ReportError(cx, last, &report); + free(last); + return warning; +} + +/* + * The arguments from ap need to be packaged up into an array and stored + * into the report struct. + * + * The format string addressed by the error number may contain operands + * identified by the format {N}, where N is a decimal digit. Each of these + * is to be replaced by the Nth argument from the va_list. The complete + * message is placed into reportp->ucmessage converted to a JSString. + * + * Returns true if the expansion succeeds (can fail if out of memory). + */ +JSBool +js_ExpandErrorArguments(JSContext *cx, JSErrorCallback callback, + void *userRef, const uintN errorNumber, + char **messagep, JSErrorReport *reportp, + JSBool *warningp, JSBool charArgs, va_list ap) +{ + const JSErrorFormatString *efs; + int i; + int argCount; + + *warningp = JSREPORT_IS_WARNING(reportp->flags); + if (*warningp && JS_HAS_WERROR_OPTION(cx)) { + reportp->flags &= ~JSREPORT_WARNING; + *warningp = JS_FALSE; + } + + *messagep = NULL; + if (callback) { + efs = callback(userRef, NULL, errorNumber); + if (efs) { + size_t totalArgsLength = 0; + size_t argLengths[10]; /* only {0} thru {9} supported */ + argCount = efs->argCount; + JS_ASSERT(argCount <= 10); + if (argCount > 0) { + /* + * Gather the arguments into an array, and accumulate + * their sizes. We allocate 1 more than necessary and + * null it out to act as the caboose when we free the + * pointers later. + */ + reportp->messageArgs = (const jschar **) + JS_malloc(cx, sizeof(jschar *) * (argCount + 1)); + if (!reportp->messageArgs) + return JS_FALSE; + reportp->messageArgs[argCount] = NULL; + for (i = 0; i < argCount; i++) { + if (charArgs) { + char *charArg = va_arg(ap, char *); + reportp->messageArgs[i] + = js_InflateString(cx, charArg, strlen(charArg)); + if (!reportp->messageArgs[i]) + goto error; + } + else + reportp->messageArgs[i] = va_arg(ap, jschar *); + argLengths[i] = js_strlen(reportp->messageArgs[i]); + totalArgsLength += argLengths[i]; + } + /* NULL-terminate for easy copying. */ + reportp->messageArgs[i] = NULL; + } + /* + * Parse the error format, substituting the argument X + * for {X} in the format. + */ + if (argCount > 0) { + if (efs->format) { + const char *fmt; + const jschar *arg; + jschar *out; + int expandedArgs = 0; + size_t expandedLength + = strlen(efs->format) + - (3 * argCount) /* exclude the {n} */ + + totalArgsLength; + /* + * Note - the above calculation assumes that each argument + * is used once and only once in the expansion !!! + */ + reportp->ucmessage = out = (jschar *) + JS_malloc(cx, (expandedLength + 1) * sizeof(jschar)); + if (!out) + goto error; + fmt = efs->format; + while (*fmt) { + if (*fmt == '{') { + if (isdigit(fmt[1])) { + int d = JS7_UNDEC(fmt[1]); + JS_ASSERT(expandedArgs < argCount); + arg = reportp->messageArgs[d]; + js_strncpy(out, arg, argLengths[d]); + out += argLengths[d]; + fmt += 3; + expandedArgs++; + continue; + } + } + /* + * is this kosher? + */ + *out++ = (unsigned char)(*fmt++); + } + JS_ASSERT(expandedArgs == argCount); + *out = 0; + *messagep = + js_DeflateString(cx, reportp->ucmessage, + (size_t)(out - reportp->ucmessage)); + if (!*messagep) + goto error; + } + } else { + /* + * Zero arguments: the format string (if it exists) is the + * entire message. + */ + if (efs->format) { + *messagep = JS_strdup(cx, efs->format); + if (!*messagep) + goto error; + reportp->ucmessage + = js_InflateString(cx, *messagep, strlen(*messagep)); + if (!reportp->ucmessage) + goto error; + } + } + } + } + if (*messagep == NULL) { + /* where's the right place for this ??? */ + const char *defaultErrorMessage + = "No error message available for error number %d"; + size_t nbytes = strlen(defaultErrorMessage) + 16; + *messagep = (char *)JS_malloc(cx, nbytes); + if (!*messagep) + goto error; + JS_snprintf(*messagep, nbytes, defaultErrorMessage, errorNumber); + } + return JS_TRUE; + +error: + if (reportp->messageArgs) { + i = 0; + while (reportp->messageArgs[i]) + JS_free(cx, (void *)reportp->messageArgs[i++]); + JS_free(cx, (void *)reportp->messageArgs); + reportp->messageArgs = NULL; + } + if (reportp->ucmessage) { + JS_free(cx, (void *)reportp->ucmessage); + reportp->ucmessage = NULL; + } + if (*messagep) { + JS_free(cx, (void *)*messagep); + *messagep = NULL; + } + return JS_FALSE; +} + +JSBool +js_ReportErrorNumberVA(JSContext *cx, uintN flags, JSErrorCallback callback, + void *userRef, const uintN errorNumber, + JSBool charArgs, va_list ap) +{ + JSStackFrame *fp; + JSErrorReport report; + char *message; + JSBool warning; + + if ((flags & JSREPORT_STRICT) && !JS_HAS_STRICT_OPTION(cx)) + return JS_TRUE; + + memset(&report, 0, sizeof (struct JSErrorReport)); + report.flags = flags; + report.errorNumber = errorNumber; + + /* + * If we can't find out where the error was based on the current frame, + * see if the next frame has a script/pc combo we can use. + */ + for (fp = cx->fp; fp; fp = fp->down) { + if (fp->script && fp->pc) { + report.filename = fp->script->filename; + report.lineno = js_PCToLineNumber(cx, fp->script, fp->pc); + break; + } + } + + if (!js_ExpandErrorArguments(cx, callback, userRef, errorNumber, + &message, &report, &warning, charArgs, ap)) { + return JS_FALSE; + } + + ReportError(cx, message, &report); + + if (message) + JS_free(cx, message); + if (report.messageArgs) { + int i = 0; + while (report.messageArgs[i]) + JS_free(cx, (void *)report.messageArgs[i++]); + JS_free(cx, (void *)report.messageArgs); + } + if (report.ucmessage) + JS_free(cx, (void *)report.ucmessage); + + return warning; +} + +JS_FRIEND_API(void) +js_ReportErrorAgain(JSContext *cx, const char *message, JSErrorReport *reportp) +{ + JSErrorReporter onError; + + if (!message) + return; + + if (cx->lastMessage) + free(cx->lastMessage); + cx->lastMessage = JS_strdup(cx, message); + if (!cx->lastMessage) + return; + onError = cx->errorReporter; + + /* + * If debugErrorHook is present then we give it a chance to veto + * sending the error on to the regular ErrorReporter. + */ + if (onError) { + JSDebugErrorHook hook = cx->runtime->debugErrorHook; + if (hook && + !hook(cx, cx->lastMessage, reportp, + cx->runtime->debugErrorHookData)) { + onError = NULL; + } + } + if (onError) + onError(cx, cx->lastMessage, reportp); +} + +void +js_ReportIsNotDefined(JSContext *cx, const char *name) +{ + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_DEFINED, name); +} + +#if defined DEBUG && defined XP_UNIX +/* For gdb usage. */ +void js_traceon(JSContext *cx) { cx->tracefp = stderr; } +void js_traceoff(JSContext *cx) { cx->tracefp = NULL; } +#endif + +JSErrorFormatString js_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 "js.msg" +#undef MSG_DEF +}; + +const JSErrorFormatString * +js_GetErrorMessage(void *userRef, const char *locale, const uintN errorNumber) +{ + if ((errorNumber > 0) && (errorNumber < JSErr_Limit)) + return &js_ErrorFormatString[errorNumber]; + return NULL; +} |
