Index: ossp-pkg/js/src/jsinterp.c RCS File: /v/ossp/cvs/ossp-pkg/js/src/jsinterp.c,v co -q -kk -p'1.1' '/v/ossp/cvs/ossp-pkg/js/src/jsinterp.c,v' | diff -u /dev/null - -L'ossp-pkg/js/src/jsinterp.c' 2>/dev/null --- ossp-pkg/js/src/jsinterp.c +++ - 2025-05-20 18:57:45.139219438 +0200 @@ -0,0 +1,5531 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=78: + * + * ***** 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 ***** */ + +/* + * JavaScript bytecode interpreter. + */ +#include "jsstddef.h" +#include +#include +#include +#include "jstypes.h" +#include "jsarena.h" /* Added by JSIFY */ +#include "jsutil.h" /* Added by JSIFY */ +#include "jsprf.h" +#include "jsapi.h" +#include "jsarray.h" +#include "jsatom.h" +#include "jsbool.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsdbgapi.h" +#include "jsfun.h" +#include "jsgc.h" +#include "jsinterp.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsscope.h" +#include "jsscript.h" +#include "jsstr.h" + +#if JS_HAS_JIT +#include "jsjit.h" +#endif + +#if JS_HAS_XML_SUPPORT +#include "jsxml.h" +#endif + +#ifdef DEBUG +#define ASSERT_CACHE_IS_EMPTY(cache) \ + JS_BEGIN_MACRO \ + JSPropertyCacheEntry *end_, *pce_, entry_; \ + JSPropertyCache *cache_ = (cache); \ + JS_ASSERT(cache_->empty); \ + end_ = &cache_->table[PROPERTY_CACHE_SIZE]; \ + for (pce_ = &cache_->table[0]; pce_ < end_; pce_++) { \ + PCE_LOAD(cache_, pce_, entry_); \ + JS_ASSERT(!PCE_OBJECT(entry_)); \ + JS_ASSERT(!PCE_PROPERTY(entry_)); \ + } \ + JS_END_MACRO +#else +#define ASSERT_CACHE_IS_EMPTY(cache) ((void)0) +#endif + +void +js_FlushPropertyCache(JSContext *cx) +{ + JSPropertyCache *cache; + + cache = &cx->runtime->propertyCache; + if (cache->empty) { + ASSERT_CACHE_IS_EMPTY(cache); + return; + } + memset(cache->table, 0, sizeof cache->table); + cache->empty = JS_TRUE; +#ifdef JS_PROPERTY_CACHE_METERING + cache->flushes++; +#endif +} + +void +js_DisablePropertyCache(JSContext *cx) +{ + JS_ASSERT(!cx->runtime->propertyCache.disabled); + cx->runtime->propertyCache.disabled = JS_TRUE; +} + +void +js_EnablePropertyCache(JSContext *cx) +{ + JS_ASSERT(cx->runtime->propertyCache.disabled); + ASSERT_CACHE_IS_EMPTY(&cx->runtime->propertyCache); + cx->runtime->propertyCache.disabled = JS_FALSE; +} + +/* + * Class for for/in loop property iterator objects. + */ +#define JSSLOT_ITER_STATE JSSLOT_PRIVATE + +static void +prop_iterator_finalize(JSContext *cx, JSObject *obj) +{ + jsval iter_state; + jsval iteratee; + + /* Protect against stillborn iterators. */ + iter_state = obj->slots[JSSLOT_ITER_STATE]; + iteratee = obj->slots[JSSLOT_PARENT]; + if (!JSVAL_IS_NULL(iter_state) && !JSVAL_IS_PRIMITIVE(iteratee)) { + OBJ_ENUMERATE(cx, JSVAL_TO_OBJECT(iteratee), JSENUMERATE_DESTROY, + &iter_state, NULL); + } + js_RemoveRoot(cx->runtime, &obj->slots[JSSLOT_PARENT]); +} + +static JSClass prop_iterator_class = { + "PropertyIterator", + 0, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, prop_iterator_finalize, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +/* + * Stack macros and functions. These all use a local variable, jsval *sp, to + * point to the next free stack slot. SAVE_SP must be called before any call + * to a function that may invoke the interpreter. RESTORE_SP must be called + * only after return from js_Invoke, because only js_Invoke changes fp->sp. + */ +#define PUSH(v) (*sp++ = (v)) +#define POP() (*--sp) +#ifdef DEBUG +#define SAVE_SP(fp) \ + (JS_ASSERT((fp)->script || !(fp)->spbase || (sp) == (fp)->spbase), \ + (fp)->sp = sp) +#else +#define SAVE_SP(fp) ((fp)->sp = sp) +#endif +#define RESTORE_SP(fp) (sp = (fp)->sp) + +/* + * Push the generating bytecode's pc onto the parallel pc stack that runs + * depth slots below the operands. + * + * NB: PUSH_OPND uses sp, depth, and pc from its lexical environment. See + * js_Interpret for these local variables' declarations and uses. + */ +#define PUSH_OPND(v) (sp[-depth] = (jsval)pc, PUSH(v)) +#define STORE_OPND(n,v) (sp[(n)-depth] = (jsval)pc, sp[n] = (v)) +#define POP_OPND() POP() +#define FETCH_OPND(n) (sp[n]) + +/* + * Push the jsdouble d using sp, depth, and pc from the lexical environment. + * Try to convert d to a jsint that fits in a jsval, otherwise GC-alloc space + * for it and push a reference. + */ +#define STORE_NUMBER(cx, n, d) \ + JS_BEGIN_MACRO \ + jsint i_; \ + jsval v_; \ + \ + if (JSDOUBLE_IS_INT(d, i_) && INT_FITS_IN_JSVAL(i_)) { \ + v_ = INT_TO_JSVAL(i_); \ + } else { \ + ok = js_NewDoubleValue(cx, d, &v_); \ + if (!ok) \ + goto out; \ + } \ + STORE_OPND(n, v_); \ + JS_END_MACRO + +#define FETCH_NUMBER(cx, n, d) \ + JS_BEGIN_MACRO \ + jsval v_; \ + \ + v_ = FETCH_OPND(n); \ + VALUE_TO_NUMBER(cx, v_, d); \ + JS_END_MACRO + +#define FETCH_INT(cx, n, i) \ + JS_BEGIN_MACRO \ + jsval v_ = FETCH_OPND(n); \ + if (JSVAL_IS_INT(v_)) { \ + i = JSVAL_TO_INT(v_); \ + } else { \ + SAVE_SP(fp); \ + ok = js_ValueToECMAInt32(cx, v_, &i); \ + if (!ok) \ + goto out; \ + } \ + JS_END_MACRO + +#define FETCH_UINT(cx, n, ui) \ + JS_BEGIN_MACRO \ + jsval v_ = FETCH_OPND(n); \ + jsint i_; \ + if (JSVAL_IS_INT(v_) && (i_ = JSVAL_TO_INT(v_)) >= 0) { \ + ui = (uint32) i_; \ + } else { \ + SAVE_SP(fp); \ + ok = js_ValueToECMAUint32(cx, v_, &ui); \ + if (!ok) \ + goto out; \ + } \ + JS_END_MACRO + +/* + * Optimized conversion macros that test for the desired type in v before + * homing sp and calling a conversion function. + */ +#define VALUE_TO_NUMBER(cx, v, d) \ + JS_BEGIN_MACRO \ + if (JSVAL_IS_INT(v)) { \ + d = (jsdouble)JSVAL_TO_INT(v); \ + } else if (JSVAL_IS_DOUBLE(v)) { \ + d = *JSVAL_TO_DOUBLE(v); \ + } else { \ + SAVE_SP(fp); \ + ok = js_ValueToNumber(cx, v, &d); \ + if (!ok) \ + goto out; \ + } \ + JS_END_MACRO + +#define POP_BOOLEAN(cx, v, b) \ + JS_BEGIN_MACRO \ + v = FETCH_OPND(-1); \ + if (v == JSVAL_NULL) { \ + b = JS_FALSE; \ + } else if (JSVAL_IS_BOOLEAN(v)) { \ + b = JSVAL_TO_BOOLEAN(v); \ + } else { \ + SAVE_SP(fp); \ + ok = js_ValueToBoolean(cx, v, &b); \ + if (!ok) \ + goto out; \ + } \ + sp--; \ + JS_END_MACRO + +#define VALUE_TO_OBJECT(cx, v, obj) \ + JS_BEGIN_MACRO \ + if (!JSVAL_IS_PRIMITIVE(v)) { \ + obj = JSVAL_TO_OBJECT(v); \ + } else { \ + SAVE_SP(fp); \ + obj = js_ValueToNonNullObject(cx, v); \ + if (!obj) { \ + ok = JS_FALSE; \ + goto out; \ + } \ + } \ + JS_END_MACRO + +#define FETCH_OBJECT(cx, n, v, obj) \ + JS_BEGIN_MACRO \ + v = FETCH_OPND(n); \ + VALUE_TO_OBJECT(cx, v, obj); \ + STORE_OPND(n, OBJECT_TO_JSVAL(obj)); \ + JS_END_MACRO + +#if JS_BUG_VOID_TOSTRING +#define CHECK_VOID_TOSTRING(cx, v) \ + if (JSVAL_IS_VOID(v)) { \ + JSString *str_; \ + str_ = ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]); \ + v = STRING_TO_JSVAL(str_); \ + } +#else +#define CHECK_VOID_TOSTRING(cx, v) ((void)0) +#endif + +#if JS_BUG_EAGER_TOSTRING +#define CHECK_EAGER_TOSTRING(hint) (hint = JSTYPE_STRING) +#else +#define CHECK_EAGER_TOSTRING(hint) ((void)0) +#endif + +#define VALUE_TO_PRIMITIVE(cx, v, hint, vp) \ + JS_BEGIN_MACRO \ + if (JSVAL_IS_PRIMITIVE(v)) { \ + CHECK_VOID_TOSTRING(cx, v); \ + *vp = v; \ + } else { \ + SAVE_SP(fp); \ + CHECK_EAGER_TOSTRING(hint); \ + ok = OBJ_DEFAULT_VALUE(cx, JSVAL_TO_OBJECT(v), hint, vp); \ + if (!ok) \ + goto out; \ + } \ + JS_END_MACRO + +JS_FRIEND_API(jsval *) +js_AllocRawStack(JSContext *cx, uintN nslots, void **markp) +{ + jsval *sp; + + if (markp) + *markp = JS_ARENA_MARK(&cx->stackPool); + JS_ARENA_ALLOCATE_CAST(sp, jsval *, &cx->stackPool, nslots * sizeof(jsval)); + if (!sp) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_STACK_OVERFLOW, + (cx->fp && cx->fp->fun) + ? JS_GetFunctionName(cx->fp->fun) + : "script"); + } + return sp; +} + +JS_FRIEND_API(void) +js_FreeRawStack(JSContext *cx, void *mark) +{ + JS_ARENA_RELEASE(&cx->stackPool, mark); +} + +JS_FRIEND_API(jsval *) +js_AllocStack(JSContext *cx, uintN nslots, void **markp) +{ + jsval *sp, *vp, *end; + JSArena *a; + JSStackHeader *sh; + JSStackFrame *fp; + + /* Callers don't check for zero nslots: we do to avoid empty segments. */ + if (nslots == 0) { + *markp = NULL; + return JS_ARENA_MARK(&cx->stackPool); + } + + /* Allocate 2 extra slots for the stack segment header we'll likely need. */ + sp = js_AllocRawStack(cx, 2 + nslots, markp); + if (!sp) + return NULL; + + /* Try to avoid another header if we can piggyback on the last segment. */ + a = cx->stackPool.current; + sh = cx->stackHeaders; + if (sh && JS_STACK_SEGMENT(sh) + sh->nslots == sp) { + /* Extend the last stack segment, give back the 2 header slots. */ + sh->nslots += nslots; + a->avail -= 2 * sizeof(jsval); + } else { + /* + * Need a new stack segment, so we must initialize unused slots in the + * current frame. See js_GC, just before marking the "operand" jsvals, + * where we scan from fp->spbase to fp->sp or through fp->script->depth + * (whichever covers fewer slots). + */ + fp = cx->fp; + if (fp && fp->script && fp->spbase) { +#ifdef DEBUG + jsuword depthdiff = fp->script->depth * sizeof(jsval); + JS_ASSERT(JS_UPTRDIFF(fp->sp, fp->spbase) <= depthdiff); + JS_ASSERT(JS_UPTRDIFF(*markp, fp->spbase) >= depthdiff); +#endif + end = fp->spbase + fp->script->depth; + for (vp = fp->sp; vp < end; vp++) + *vp = JSVAL_VOID; + } + + /* Allocate and push a stack segment header from the 2 extra slots. */ + sh = (JSStackHeader *)sp; + sh->nslots = nslots; + sh->down = cx->stackHeaders; + cx->stackHeaders = sh; + sp += 2; + } + + /* + * Store JSVAL_NULL using memset, to let compilers optimize as they see + * fit, in case a caller allocates and pushes GC-things one by one, which + * could nest a last-ditch GC that will scan this segment. + */ + memset(sp, 0, nslots * sizeof(jsval)); + return sp; +} + +JS_FRIEND_API(void) +js_FreeStack(JSContext *cx, void *mark) +{ + JSStackHeader *sh; + jsuword slotdiff; + + /* Check for zero nslots allocation special case. */ + if (!mark) + return; + + /* We can assert because js_FreeStack always balances js_AllocStack. */ + sh = cx->stackHeaders; + JS_ASSERT(sh); + + /* If mark is in the current segment, reduce sh->nslots, else pop sh. */ + slotdiff = JS_UPTRDIFF(mark, JS_STACK_SEGMENT(sh)) / sizeof(jsval); + if (slotdiff < (jsuword)sh->nslots) + sh->nslots = slotdiff; + else + cx->stackHeaders = sh->down; + + /* Release the stackPool space allocated since mark was set. */ + JS_ARENA_RELEASE(&cx->stackPool, mark); +} + +JSBool +js_GetArgument(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + return JS_TRUE; +} + +JSBool +js_SetArgument(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + return JS_TRUE; +} + +JSBool +js_GetLocalVariable(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + return JS_TRUE; +} + +JSBool +js_SetLocalVariable(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + return JS_TRUE; +} + +JSBool +js_ComputeThis(JSContext *cx, JSObject *thisp, JSStackFrame *fp) +{ + if (thisp && OBJ_GET_CLASS(cx, thisp) != &js_CallClass) { + /* Some objects (e.g., With) delegate 'this' to another object. */ + thisp = OBJ_THIS_OBJECT(cx, thisp); + if (!thisp) + return JS_FALSE; + + /* Default return value for a constructor is the new object. */ + if (fp->flags & JSFRAME_CONSTRUCTING) + fp->rval = OBJECT_TO_JSVAL(thisp); + } else { + /* + * ECMA requires "the global object", but in the presence of multiple + * top-level objects (windows, frames, or certain layers in the client + * object model), we prefer fun's parent. An example that causes this + * code to run: + * + * // in window w1 + * function f() { return this } + * function g() { return f } + * + * // in window w2 + * var h = w1.g() + * alert(h() == w1) + * + * The alert should display "true". + */ + JS_ASSERT(!(fp->flags & JSFRAME_CONSTRUCTING)); + if (JSVAL_IS_PRIMITIVE(fp->argv[-2]) || + !OBJ_GET_PARENT(cx, JSVAL_TO_OBJECT(fp->argv[-2]))) { + thisp = cx->globalObject; + } else { + jsid id; + jsval v; + uintN attrs; + + /* Walk up the parent chain. */ + thisp = JSVAL_TO_OBJECT(fp->argv[-2]); + id = ATOM_TO_JSID(cx->runtime->atomState.parentAtom); + for (;;) { + if (!OBJ_CHECK_ACCESS(cx, thisp, id, JSACC_PARENT, &v, &attrs)) + return JS_FALSE; + if (JSVAL_IS_VOID(v)) + v = OBJ_GET_SLOT(cx, thisp, JSSLOT_PARENT); + JS_ASSERT(JSVAL_TO_OBJECT(v) == OBJ_GET_PARENT(cx, thisp)); + if (JSVAL_IS_NULL(v)) + break; + thisp = JSVAL_TO_OBJECT(v); + } + } + } + fp->thisp = thisp; + fp->argv[-1] = OBJECT_TO_JSVAL(thisp); + return JS_TRUE; +} + +#ifdef DUMP_CALL_TABLE + +#include "jsclist.h" +#include "jshash.h" +#include "jsdtoa.h" + +typedef struct CallKey { + jsval callee; /* callee value */ + const char *filename; /* function filename or null */ + uintN lineno; /* function lineno or 0 */ +} CallKey; + +/* Compensate for typeof null == "object" brain damage. */ +#define JSTYPE_NULL JSTYPE_LIMIT +#define TYPEOF(cx,v) (JSVAL_IS_NULL(v) ? JSTYPE_NULL : JS_TypeOfValue(cx,v)) +#define TYPENAME(t) (((t) == JSTYPE_NULL) ? js_null_str : js_type_str[t]) +#define NTYPEHIST (JSTYPE_LIMIT + 1) + +typedef struct CallValue { + uint32 total; /* total call count */ + uint32 recycled; /* LRU-recycled calls lost */ + uint16 minargc; /* minimum argument count */ + uint16 maxargc; /* maximum argument count */ + struct ArgInfo { + uint32 typeHist[NTYPEHIST]; /* histogram by type */ + JSCList lruList; /* top 10 values LRU list */ + struct ArgValCount { + JSCList lruLink; /* LRU list linkage */ + jsval value; /* recently passed value */ + uint32 count; /* number of times passed */ + char strbuf[112]; /* string conversion buffer */ + } topValCounts[10]; /* top 10 value storage */ + } argInfo[8]; +} CallValue; + +typedef struct CallEntry { + JSHashEntry entry; + CallKey key; + CallValue value; + char name[32]; /* function name copy */ +} CallEntry; + +static void * +AllocCallTable(void *pool, size_t size) +{ + return malloc(size); +} + +static void +FreeCallTable(void *pool, void *item) +{ + free(item); +} + +static JSHashEntry * +AllocCallEntry(void *pool, const void *key) +{ + return (JSHashEntry*) calloc(1, sizeof(CallEntry)); +} + +static void +FreeCallEntry(void *pool, JSHashEntry *he, uintN flag) +{ + JS_ASSERT(flag == HT_FREE_ENTRY); + free(he); +} + +static JSHashAllocOps callTableAllocOps = { + AllocCallTable, FreeCallTable, + AllocCallEntry, FreeCallEntry +}; + +JS_STATIC_DLL_CALLBACK(JSHashNumber) +js_hash_call_key(const void *key) +{ + CallKey *ck = (CallKey *) key; + JSHashNumber hash = (jsuword)ck->callee >> 3; + + if (ck->filename) { + hash = (hash << 4) ^ JS_HashString(ck->filename); + hash = (hash << 4) ^ ck->lineno; + } + return hash; +} + +JS_STATIC_DLL_CALLBACK(intN) +js_compare_call_keys(const void *k1, const void *k2) +{ + CallKey *ck1 = (CallKey *)k1, *ck2 = (CallKey *)k2; + + return ck1->callee == ck2->callee && + ((ck1->filename && ck2->filename) + ? strcmp(ck1->filename, ck2->filename) == 0 + : ck1->filename == ck2->filename) && + ck1->lineno == ck2->lineno; +} + +JSHashTable *js_CallTable; +size_t js_LogCallToSourceLimit; + +JS_STATIC_DLL_CALLBACK(intN) +CallTableDumper(JSHashEntry *he, intN k, void *arg) +{ + CallEntry *ce = (CallEntry *)he; + FILE *fp = (FILE *)arg; + uintN argc, i, n; + struct ArgInfo *ai; + JSType save, type; + JSCList *cl; + struct ArgValCount *avc; + jsval argval; + + if (ce->key.filename) { + /* We're called at the end of the mark phase, so mark our filenames. */ + js_MarkScriptFilename(ce->key.filename); + fprintf(fp, "%s:%u ", ce->key.filename, ce->key.lineno); + } else { + fprintf(fp, "@%p ", (void *) ce->key.callee); + } + + if (ce->name[0]) + fprintf(fp, "name %s ", ce->name); + fprintf(fp, "calls %lu (%lu) argc %u/%u\n", + (unsigned long) ce->value.total, + (unsigned long) ce->value.recycled, + ce->value.minargc, ce->value.maxargc); + + argc = JS_MIN(ce->value.maxargc, 8); + for (i = 0; i < argc; i++) { + ai = &ce->value.argInfo[i]; + + n = 0; + save = -1; + for (type = JSTYPE_VOID; type <= JSTYPE_LIMIT; type++) { + if (ai->typeHist[type]) { + save = type; + ++n; + } + } + if (n == 1) { + fprintf(fp, " arg %u type %s: %lu\n", + i, TYPENAME(save), (unsigned long) ai->typeHist[save]); + } else { + fprintf(fp, " arg %u type histogram:\n", i); + for (type = JSTYPE_VOID; type <= JSTYPE_LIMIT; type++) { + fprintf(fp, " %9s: %8lu ", + TYPENAME(type), (unsigned long) ai->typeHist[type]); + for (n = (uintN) JS_HOWMANY(ai->typeHist[type], 10); n > 0; --n) + fputc('*', fp); + fputc('\n', fp); + } + } + + fprintf(fp, " arg %u top 10 values:\n", i); + n = 1; + for (cl = ai->lruList.prev; cl != &ai->lruList; cl = cl->prev) { + avc = (struct ArgValCount *)cl; + if (!avc->count) + break; + argval = avc->value; + fprintf(fp, " %9u: %8lu %.*s (%#lx)\n", + n, (unsigned long) avc->count, + sizeof avc->strbuf, avc->strbuf, argval); + ++n; + } + } + + return HT_ENUMERATE_NEXT; +} + +void +js_DumpCallTable(JSContext *cx) +{ + char name[24]; + FILE *fp; + static uintN dumpCount; + + if (!js_CallTable) + return; + + JS_snprintf(name, sizeof name, "/tmp/calltable.dump.%u", dumpCount & 7); + dumpCount++; + fp = fopen(name, "w"); + if (!fp) + return; + + JS_HashTableEnumerateEntries(js_CallTable, CallTableDumper, fp); + fclose(fp); +} + +static void +LogCall(JSContext *cx, jsval callee, uintN argc, jsval *argv) +{ + CallKey key; + const char *name, *cstr; + JSFunction *fun; + JSHashNumber keyHash; + JSHashEntry **hep, *he; + CallEntry *ce; + uintN i, j; + jsval argval; + JSType type; + struct ArgInfo *ai; + struct ArgValCount *avc; + JSString *str; + + if (!js_CallTable) { + js_CallTable = JS_NewHashTable(1024, js_hash_call_key, + js_compare_call_keys, NULL, + &callTableAllocOps, NULL); + if (!js_CallTable) + return; + } + + key.callee = callee; + key.filename = NULL; + key.lineno = 0; + name = ""; + if (JSVAL_IS_FUNCTION(cx, callee)) { + fun = (JSFunction *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(callee)); + if (fun->atom) + name = js_AtomToPrintableString(cx, fun->atom); + if (fun->interpreted) { + key.filename = fun->u.script->filename; + key.lineno = fun->u.script->lineno; + } + } + keyHash = js_hash_call_key(&key); + + hep = JS_HashTableRawLookup(js_CallTable, keyHash, &key); + he = *hep; + if (he) { + ce = (CallEntry *) he; + JS_ASSERT(strncmp(ce->name, name, sizeof ce->name) == 0); + } else { + he = JS_HashTableRawAdd(js_CallTable, hep, keyHash, &key, NULL); + if (!he) + return; + ce = (CallEntry *) he; + ce->entry.key = &ce->key; + ce->entry.value = &ce->value; + ce->key = key; + for (i = 0; i < 8; i++) { + ai = &ce->value.argInfo[i]; + JS_INIT_CLIST(&ai->lruList); + for (j = 0; j < 10; j++) + JS_APPEND_LINK(&ai->topValCounts[j].lruLink, &ai->lruList); + } + strncpy(ce->name, name, sizeof ce->name); + } + + ++ce->value.total; + if (ce->value.minargc < argc) + ce->value.minargc = argc; + if (ce->value.maxargc < argc) + ce->value.maxargc = argc; + if (argc > 8) + argc = 8; + for (i = 0; i < argc; i++) { + ai = &ce->value.argInfo[i]; + argval = argv[i]; + type = TYPEOF(cx, argval); + ++ai->typeHist[type]; + + for (j = 0; ; j++) { + if (j == 10) { + avc = (struct ArgValCount *) ai->lruList.next; + ce->value.recycled += avc->count; + avc->value = argval; + avc->count = 1; + break; + } + avc = &ai->topValCounts[j]; + if (avc->value == argval) { + ++avc->count; + break; + } + } + + /* Move avc to the back of the LRU list. */ + JS_REMOVE_LINK(&avc->lruLink); + JS_APPEND_LINK(&avc->lruLink, &ai->lruList); + + str = NULL; + cstr = ""; + switch (TYPEOF(cx, argval)) { + case JSTYPE_VOID: + cstr = js_type_str[JSTYPE_VOID]; + break; + case JSTYPE_NULL: + cstr = js_null_str; + break; + case JSTYPE_BOOLEAN: + cstr = js_boolean_str[JSVAL_TO_BOOLEAN(argval)]; + break; + case JSTYPE_NUMBER: + if (JSVAL_IS_INT(argval)) { + JS_snprintf(avc->strbuf, sizeof avc->strbuf, "%ld", + JSVAL_TO_INT(argval)); + } else { + JS_dtostr(avc->strbuf, sizeof avc->strbuf, DTOSTR_STANDARD, 0, + *JSVAL_TO_DOUBLE(argval)); + } + continue; + case JSTYPE_STRING: + str = js_QuoteString(cx, JSVAL_TO_STRING(argval), (jschar)'"'); + break; + case JSTYPE_FUNCTION: + if (JSVAL_IS_FUNCTION(cx, argval)) { + fun = (JSFunction *)JS_GetPrivate(cx, JSVAL_TO_OBJECT(argval)); + if (fun && fun->atom) { + str = ATOM_TO_STRING(fun->atom); + break; + } + } + /* FALL THROUGH */ + case JSTYPE_OBJECT: + js_LogCallToSourceLimit = sizeof avc->strbuf; + cx->options |= JSOPTION_LOGCALL_TOSOURCE; + str = js_ValueToSource(cx, argval); + cx->options &= ~JSOPTION_LOGCALL_TOSOURCE; + break; + } + if (str) + cstr = JS_GetStringBytes(str); + strncpy(avc->strbuf, cstr, sizeof avc->strbuf); + } +} + +#endif /* DUMP_CALL_TABLE */ + +/* + * Find a function reference and its 'this' object implicit first parameter + * under argc arguments on cx's stack, and call the function. Push missing + * required arguments, allocate declared local variables, and pop everything + * when done. Then push the return value. + */ +JS_FRIEND_API(JSBool) +js_Invoke(JSContext *cx, uintN argc, uintN flags) +{ + void *mark; + JSStackFrame *fp, frame; + jsval *sp, *newsp, *limit; + jsval *vp, v; + JSObject *funobj, *parent, *thisp; + JSBool ok; + JSClass *clasp; + JSObjectOps *ops; + JSNative native; + JSFunction *fun; + JSScript *script; + uintN nslots, nvars, nalloc, surplus; + JSInterpreterHook hook; + void *hookData; + + /* Mark the top of stack and load frequently-used registers. */ + mark = JS_ARENA_MARK(&cx->stackPool); + fp = cx->fp; + sp = fp->sp; + + /* + * Set vp to the callee value's stack slot (it's where rval goes). + * Once vp is set, control should flow through label out2: to return. + * Set frame.rval early so native class and object ops can throw and + * return false, causing a goto out2 with ok set to false. Also set + * frame.flags to flags so that ComputeThis can test bits in it. + */ + vp = sp - (2 + argc); + v = *vp; + frame.rval = JSVAL_VOID; + frame.flags = flags; + thisp = JSVAL_TO_OBJECT(vp[1]); + + /* + * A callee must be an object reference, unless its |this| parameter + * implements the __noSuchMethod__ method, in which case that method will + * be called like so: + * + * thisp.__noSuchMethod__(id, args) + * + * where id is the name of the method that this invocation attempted to + * call by name, and args is an Array containing this invocation's actual + * parameters. + */ + if (JSVAL_IS_PRIMITIVE(v)) { +#if JS_HAS_NO_SUCH_METHOD + jsid id; + jsbytecode *pc; + jsatomid atomIndex; + JSAtom *atom; + JSObject *argsobj; + JSArena *a; + + if (!fp->script || (flags & JSINVOKE_INTERNAL)) + goto bad; + + /* + * We must ComputeThis here to censor Call objects; performance hit, + * but at least it's idempotent. + * + * Normally, we call ComputeThis after all frame members have been + * set, and in particular, after any revision of the callee value at + * *vp due to clasp->convert (see below). This matters because + * ComputeThis may access *vp via fp->argv[-2], to follow the parent + * chain to a global object to use as the |this| parameter. + * + * Obviously, here in the JSVAL_IS_PRIMITIVE(v) case, there can't be + * any such defaulting of |this| to callee (v, *vp) ancestor. + */ + frame.argv = vp + 2; + ok = js_ComputeThis(cx, thisp, &frame); + if (!ok) + goto out2; + thisp = frame.thisp; + + id = ATOM_TO_JSID(cx->runtime->atomState.noSuchMethodAtom); + if (OBJECT_IS_XML(cx, thisp)) { + JSXMLObjectOps *ops; + + ops = (JSXMLObjectOps *) thisp->map->ops; + thisp = ops->getMethod(cx, thisp, id, &v); + if (!thisp) { + ok = JS_FALSE; + goto out2; + } + vp[1] = OBJECT_TO_JSVAL(thisp); + } else { + ok = OBJ_GET_PROPERTY(cx, thisp, id, &v); + } + if (!ok) + goto out2; + if (JSVAL_IS_PRIMITIVE(v)) + goto bad; + + pc = (jsbytecode *) vp[-(intN)fp->script->depth]; + switch ((JSOp) *pc) { + case JSOP_NAME: + case JSOP_GETPROP: +#if JS_HAS_XML_SUPPORT + case JSOP_GETMETHOD: +#endif + atomIndex = GET_ATOM_INDEX(pc); + atom = js_GetAtom(cx, &fp->script->atomMap, atomIndex); + argsobj = js_NewArrayObject(cx, argc, vp + 2); + if (!argsobj) { + ok = JS_FALSE; + goto out2; + } + + sp = vp + 4; + if (argc < 2) { + a = cx->stackPool.current; + if ((jsuword)sp > a->limit) { + /* + * Arguments must be contiguous, and must include argv[-1] + * and argv[-2], so allocate more stack, advance sp, and + * set newsp[1] to thisp (vp[1]). The other argv elements + * will be set below, using negative indexing from sp. + */ + newsp = js_AllocRawStack(cx, 4, NULL); + if (!newsp) { + ok = JS_FALSE; + goto out2; + } + newsp[1] = OBJECT_TO_JSVAL(thisp); + sp = newsp + 4; + } else if ((jsuword)sp > a->avail) { + /* + * Inline, optimized version of JS_ARENA_ALLOCATE to claim + * the small number of words not already allocated as part + * of the caller's operand stack. + */ + JS_ArenaCountAllocation(&cx->stackPool, + (jsuword)sp - a->avail); + a->avail = (jsuword)sp; + } + } + + sp[-4] = v; + JS_ASSERT(sp[-3] == OBJECT_TO_JSVAL(thisp)); + sp[-2] = ATOM_KEY(atom); + sp[-1] = OBJECT_TO_JSVAL(argsobj); + fp->sp = sp; + argc = 2; + break; + + default: + goto bad; + } +#else + goto bad; +#endif + } + + funobj = JSVAL_TO_OBJECT(v); + parent = OBJ_GET_PARENT(cx, funobj); + clasp = OBJ_GET_CLASS(cx, funobj); + if (clasp != &js_FunctionClass) { + /* Function is inlined, all other classes use object ops. */ + ops = funobj->map->ops; + + /* + * XXX this makes no sense -- why convert to function if clasp->call? + * XXX better to call that hook without converting + * XXX the only thing that needs fixing is liveconnect + * + * Try converting to function, for closure and API compatibility. + * We attempt the conversion under all circumstances for 1.2, but + * only if there is a call op defined otherwise. + */ + if (JS_VERSION_IS_1_2(cx) || + ((ops == &js_ObjectOps) ? clasp->call : ops->call)) { + ok = clasp->convert(cx, funobj, JSTYPE_FUNCTION, &v); + if (!ok) + goto out2; + + if (JSVAL_IS_FUNCTION(cx, v)) { + /* Make vp refer to funobj to keep it available as argv[-2]. */ + *vp = v; + funobj = JSVAL_TO_OBJECT(v); + parent = OBJ_GET_PARENT(cx, funobj); + goto have_fun; + } + } + fun = NULL; + script = NULL; + nslots = nvars = 0; + + /* Try a call or construct native object op. */ + native = (flags & JSINVOKE_CONSTRUCT) ? ops->construct : ops->call; + if (!native) + goto bad; + } else { +have_fun: + /* Get private data and set derived locals from it. */ + fun = (JSFunction *) JS_GetPrivate(cx, funobj); + if (fun->interpreted) { + native = NULL; + script = fun->u.script; + } else { + native = fun->u.native; + script = NULL; + } + nslots = (fun->nargs > argc) ? fun->nargs - argc : 0; + nslots += fun->extra; + nvars = fun->nvars; + + /* Handle bound method special case. */ + if (fun->flags & JSFUN_BOUND_METHOD) + thisp = parent; + } + + /* Initialize the rest of frame, except for sp (set by SAVE_SP later). */ + frame.varobj = NULL; + frame.callobj = frame.argsobj = NULL; + frame.script = script; + frame.fun = fun; + frame.argc = argc; + frame.argv = sp - argc; + frame.nvars = nvars; + frame.vars = sp; + frame.down = fp; + frame.annotation = NULL; + frame.scopeChain = NULL; /* set below for real, after cx->fp is set */ + frame.pc = NULL; + frame.spbase = NULL; + frame.sharpDepth = 0; + frame.sharpArray = NULL; + frame.dormantNext = NULL; + frame.xmlNamespace = NULL; + + /* Compute the 'this' parameter and store it in frame as frame.thisp. */ + ok = js_ComputeThis(cx, thisp, &frame); + if (!ok) + goto out2; + + /* From here on, control must flow through label out: to return. */ + cx->fp = &frame; + + /* Init these now in case we goto out before first hook call. */ + hook = cx->runtime->callHook; + hookData = NULL; + + /* Check for argument slots required by the function. */ + if (nslots) { + /* All arguments must be contiguous, so we may have to copy actuals. */ + nalloc = nslots; + limit = (jsval *) cx->stackPool.current->limit; + if (sp + nslots > limit) { + /* Hit end of arena: we have to copy argv[-2..(argc+nslots-1)]. */ + nalloc += 2 + argc; + } else { + /* Take advantage of surplus slots in the caller's frame depth. */ + JS_ASSERT((jsval *)mark >= sp); + surplus = (jsval *)mark - sp; + nalloc -= surplus; + } + + /* Check whether we have enough space in the caller's frame. */ + if ((intN)nalloc > 0) { + /* Need space for actuals plus missing formals minus surplus. */ + newsp = js_AllocRawStack(cx, nalloc, NULL); + if (!newsp) { + ok = JS_FALSE; + goto out; + } + + /* If we couldn't allocate contiguous args, copy actuals now. */ + if (newsp != mark) { + JS_ASSERT(sp + nslots > limit); + JS_ASSERT(2 + argc + nslots == nalloc); + *newsp++ = vp[0]; + *newsp++ = vp[1]; + if (argc) + memcpy(newsp, frame.argv, argc * sizeof(jsval)); + frame.argv = newsp; + sp = frame.vars = newsp + argc; + } + } + + /* Advance frame.vars to make room for the missing args. */ + frame.vars += nslots; + + /* Push void to initialize missing args. */ + do { + PUSH(JSVAL_VOID); + } while (--nslots != 0); + } + JS_ASSERT(nslots == 0); + + /* Now allocate stack space for local variables. */ + if (nvars) { + JS_ASSERT((jsval *)cx->stackPool.current->avail >= frame.vars); + surplus = (jsval *)cx->stackPool.current->avail - frame.vars; + if (surplus < nvars) { + newsp = js_AllocRawStack(cx, nvars, NULL); + if (!newsp) { + ok = JS_FALSE; + goto out; + } + if (newsp != sp) { + /* NB: Discontinuity between argv and vars. */ + sp = frame.vars = newsp; + } + } + + /* Push void to initialize local variables. */ + do { + PUSH(JSVAL_VOID); + } while (--nvars != 0); + } + JS_ASSERT(nvars == 0); + + /* Store the current sp in frame before calling fun. */ + SAVE_SP(&frame); + + /* call the hook if present */ + if (hook && (native || script)) + hookData = hook(cx, &frame, JS_TRUE, 0, cx->runtime->callHookData); + + /* Call the function, either a native method or an interpreted script. */ + if (native) { +#if JS_HAS_LVALUE_RETURN + /* Set by JS_SetCallReturnValue2, used to return reference types. */ + cx->rval2set = JS_FALSE; +#endif + + /* If native, use caller varobj and scopeChain for eval. */ + frame.varobj = fp->varobj; + frame.scopeChain = fp->scopeChain; + ok = native(cx, frame.thisp, argc, frame.argv, &frame.rval); + JS_RUNTIME_METER(cx->runtime, nativeCalls); + } else if (script) { +#ifdef DUMP_CALL_TABLE + LogCall(cx, *vp, argc, frame.argv); +#endif + /* Use parent scope so js_GetCallObject can find the right "Call". */ + frame.scopeChain = parent; + if (fun->flags & JSFUN_HEAVYWEIGHT) { +#if JS_HAS_CALL_OBJECT + /* Scope with a call object parented by the callee's parent. */ + if (!js_GetCallObject(cx, &frame, parent)) { + ok = JS_FALSE; + goto out; + } +#else + /* Bad old code used the function as a proxy for all calls to it. */ + frame.scopeChain = funobj; +#endif + } + ok = js_Interpret(cx, script->code, &v); + } else { + /* fun might be onerror trying to report a syntax error in itself. */ + frame.scopeChain = NULL; + ok = JS_TRUE; + } + +out: + if (hookData) { + hook = cx->runtime->callHook; + if (hook) + hook(cx, &frame, JS_FALSE, &ok, hookData); + } +#if JS_HAS_CALL_OBJECT + /* If frame has a call object, sync values and clear back-pointer. */ + if (frame.callobj) + ok &= js_PutCallObject(cx, &frame); +#endif +#if JS_HAS_ARGS_OBJECT + /* If frame has an arguments object, sync values and clear back-pointer. */ + if (frame.argsobj) + ok &= js_PutArgsObject(cx, &frame); +#endif + + /* Restore cx->fp now that we're done releasing frame objects. */ + cx->fp = fp; + +out2: + /* Pop everything we may have allocated off the stack. */ + JS_ARENA_RELEASE(&cx->stackPool, mark); + + /* Store the return value and restore sp just above it. */ + *vp = frame.rval; + fp->sp = vp + 1; + + /* + * Store the location of the JSOP_CALL or JSOP_EVAL that generated the + * return value, but only if this is an external (compiled from script + * source) call that has stack budget for the generating pc. + */ + if (fp->script && !(flags & JSINVOKE_INTERNAL)) + vp[-(intN)fp->script->depth] = (jsval)fp->pc; + return ok; + +bad: + js_ReportIsNotFunction(cx, vp, flags & JSINVOKE_CONSTRUCT); + ok = JS_FALSE; + goto out2; +} + +JSBool +js_InternalInvoke(JSContext *cx, JSObject *obj, jsval fval, uintN flags, + uintN argc, jsval *argv, jsval *rval) +{ + JSStackFrame *fp, *oldfp, frame; + jsval *oldsp, *sp; + void *mark; + uintN i; + JSBool ok; + + fp = oldfp = cx->fp; + if (!fp) { + memset(&frame, 0, sizeof frame); + cx->fp = fp = &frame; + } + oldsp = fp->sp; + sp = js_AllocStack(cx, 2 + argc, &mark); + if (!sp) { + ok = JS_FALSE; + goto out; + } + + PUSH(fval); + PUSH(OBJECT_TO_JSVAL(obj)); + for (i = 0; i < argc; i++) + PUSH(argv[i]); + SAVE_SP(fp); + ok = js_Invoke(cx, argc, flags | JSINVOKE_INTERNAL); + if (ok) { + RESTORE_SP(fp); + + /* + * Store *rval in the a scoped local root if a scope is open, else in + * the cx->lastInternalResult pigeon-hole GC root, solely so users of + * js_InternalInvoke and its direct and indirect (js_ValueToString for + * example) callers do not need to manage roots for local, temporary + * references to such results. + */ + *rval = POP_OPND(); + if (JSVAL_IS_GCTHING(*rval)) { + if (cx->localRootStack) { + if (js_PushLocalRoot(cx, cx->localRootStack, *rval) < 0) + ok = JS_FALSE; + } else { + cx->lastInternalResult = *rval; + } + } + } + + js_FreeStack(cx, mark); +out: + fp->sp = oldsp; + if (oldfp != fp) + cx->fp = oldfp; + + return ok; +} + +JSBool +js_InternalGetOrSet(JSContext *cx, JSObject *obj, jsid id, jsval fval, + JSAccessMode mode, uintN argc, jsval *argv, jsval *rval) +{ + /* + * Check general (not object-ops/class-specific) access from the running + * script to obj.id only if id has a scripted getter or setter that we're + * about to invoke. If we don't check this case, nothing else will -- no + * other native code has the chance to check. + * + * Contrast this non-native (scripted) case with native getter and setter + * accesses, where the native itself must do an access check, if security + * policies requires it. We make a checkAccess or checkObjectAccess call + * back to the embedding program only in those cases where we're not going + * to call an embedding-defined native function, getter, setter, or class + * hook anyway. Where we do call such a native, there's no need for the + * engine to impose a separate access check callback on all embeddings -- + * many embeddings have no security policy at all. + */ + JS_ASSERT(mode == JSACC_READ || mode == JSACC_WRITE); + if (cx->runtime->checkObjectAccess && + JSVAL_IS_FUNCTION(cx, fval) && + ((JSFunction *)JS_GetPrivate(cx, JSVAL_TO_OBJECT(fval)))->interpreted && + !cx->runtime->checkObjectAccess(cx, obj, ID_TO_VALUE(id), mode, + &fval)) { + return JS_FALSE; + } + + return js_InternalCall(cx, obj, fval, argc, argv, rval); +} + +JSBool +js_Execute(JSContext *cx, JSObject *chain, JSScript *script, + JSStackFrame *down, uintN flags, jsval *result) +{ + JSInterpreterHook hook; + void *hookData, *mark; + JSStackFrame *oldfp, frame; + JSObject *obj, *tmp; + JSBool ok; + + hook = cx->runtime->executeHook; + hookData = mark = NULL; + oldfp = cx->fp; + frame.callobj = frame.argsobj = NULL; + frame.script = script; + if (down) { + /* Propagate arg/var state for eval and the debugger API. */ + frame.varobj = down->varobj; + frame.fun = down->fun; + frame.thisp = down->thisp; + frame.argc = down->argc; + frame.argv = down->argv; + frame.nvars = down->nvars; + frame.vars = down->vars; + frame.annotation = down->annotation; + frame.sharpArray = down->sharpArray; + } else { + obj = chain; + if (cx->options & JSOPTION_VAROBJFIX) { + while ((tmp = OBJ_GET_PARENT(cx, obj)) != NULL) + obj = tmp; + } + frame.varobj = obj; + frame.fun = NULL; + frame.thisp = chain; + frame.argc = 0; + frame.argv = NULL; + frame.nvars = script->numGlobalVars; + if (frame.nvars) { + frame.vars = js_AllocRawStack(cx, frame.nvars, &mark); + if (!frame.vars) + return JS_FALSE; + memset(frame.vars, 0, frame.nvars * sizeof(jsval)); + } else { + frame.vars = NULL; + } + frame.annotation = NULL; + frame.sharpArray = NULL; + } + frame.rval = JSVAL_VOID; + frame.down = down; + frame.scopeChain = chain; + frame.pc = NULL; + frame.sp = oldfp ? oldfp->sp : NULL; + frame.spbase = NULL; + frame.sharpDepth = 0; + frame.flags = flags; + frame.dormantNext = NULL; + frame.xmlNamespace = NULL; + + /* + * Here we wrap the call to js_Interpret with code to (conditionally) + * save and restore the old stack frame chain into a chain of 'dormant' + * frame chains. Since we are replacing cx->fp, we were running into + * the problem that if GC was called under this frame, some of the GC + * things associated with the old frame chain (available here only in + * the C variable 'oldfp') were not rooted and were being collected. + * + * So, now we preserve the links to these 'dormant' frame chains in cx + * before calling js_Interpret and cleanup afterwards. The GC walks + * these dormant chains and marks objects in the same way that it marks + * objects in the primary cx->fp chain. + */ + if (oldfp && oldfp != down) { + JS_ASSERT(!oldfp->dormantNext); + oldfp->dormantNext = cx->dormantFrameChain; + cx->dormantFrameChain = oldfp; + } + + cx->fp = &frame; + if (hook) + hookData = hook(cx, &frame, JS_TRUE, 0, cx->runtime->executeHookData); + + /* + * Use frame.rval, not result, so the last result stays rooted across any + * GC activations nested within this js_Interpret. + */ + ok = js_Interpret(cx, script->code, &frame.rval); + *result = frame.rval; + + if (hookData) { + hook = cx->runtime->executeHook; + if (hook) + hook(cx, &frame, JS_FALSE, &ok, hookData); + } + if (mark) + js_FreeRawStack(cx, mark); + cx->fp = oldfp; + + if (oldfp && oldfp != down) { + JS_ASSERT(cx->dormantFrameChain == oldfp); + cx->dormantFrameChain = oldfp->dormantNext; + oldfp->dormantNext = NULL; + } + + return ok; +} + +#if JS_HAS_EXPORT_IMPORT +/* + * If id is JSVAL_VOID, import all exported properties from obj. + */ +static JSBool +ImportProperty(JSContext *cx, JSObject *obj, jsid id) +{ + JSBool ok; + JSIdArray *ida; + JSProperty *prop; + JSObject *obj2, *target, *funobj, *closure; + JSString *str; + uintN attrs; + jsint i; + jsval value; + + if (JSVAL_IS_VOID(id)) { + ida = JS_Enumerate(cx, obj); + if (!ida) + return JS_FALSE; + ok = JS_TRUE; + if (ida->length == 0) + goto out; + } else { + ida = NULL; + if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop)) + return JS_FALSE; + if (!prop) { + str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, + ID_TO_VALUE(id), NULL); + if (str) + js_ReportIsNotDefined(cx, JS_GetStringBytes(str)); + return JS_FALSE; + } + ok = OBJ_GET_ATTRIBUTES(cx, obj, id, prop, &attrs); + OBJ_DROP_PROPERTY(cx, obj2, prop); + if (!ok) + return JS_FALSE; + if (!(attrs & JSPROP_EXPORTED)) { + str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, + ID_TO_VALUE(id), NULL); + if (str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_NOT_EXPORTED, + JS_GetStringBytes(str)); + } + return JS_FALSE; + } + } + + target = cx->fp->varobj; + i = 0; + do { + if (ida) { + id = ida->vector[i]; + ok = OBJ_GET_ATTRIBUTES(cx, obj, id, NULL, &attrs); + if (!ok) + goto out; + if (!(attrs & JSPROP_EXPORTED)) + continue; + } + ok = OBJ_CHECK_ACCESS(cx, obj, id, JSACC_IMPORT, &value, &attrs); + if (!ok) + goto out; + if (JSVAL_IS_FUNCTION(cx, value)) { + funobj = JSVAL_TO_OBJECT(value); + closure = js_CloneFunctionObject(cx, funobj, obj); + if (!closure) { + ok = JS_FALSE; + goto out; + } + value = OBJECT_TO_JSVAL(closure); + } + + /* + * Handle the case of importing a property that refers to a local + * variable or formal parameter of a function activation. These + * properties are accessed by opcodes using stack slot numbers + * generated by the compiler rather than runtime name-lookup. These + * local references, therefore, bypass the normal scope chain lookup. + * So, instead of defining a new property in the activation object, + * modify the existing value in the stack slot. + */ + if (OBJ_GET_CLASS(cx, target) == &js_CallClass) { + ok = OBJ_LOOKUP_PROPERTY(cx, target, id, &obj2, &prop); + if (!ok) + goto out; + } else { + prop = NULL; + } + if (prop && target == obj2) { + ok = OBJ_SET_PROPERTY(cx, target, id, &value); + } else { + ok = OBJ_DEFINE_PROPERTY(cx, target, id, value, NULL, NULL, + attrs & ~JSPROP_EXPORTED, + NULL); + } + if (prop) + OBJ_DROP_PROPERTY(cx, obj2, prop); + if (!ok) + goto out; + } while (ida && ++i < ida->length); + +out: + if (ida) + JS_DestroyIdArray(cx, ida); + return ok; +} +#endif /* JS_HAS_EXPORT_IMPORT */ + +JSBool +js_CheckRedeclaration(JSContext *cx, JSObject *obj, jsid id, uintN attrs, + JSObject **objp, JSProperty **propp) +{ + JSObject *obj2; + JSProperty *prop; + uintN oldAttrs, report; + JSBool isFunction; + jsval value; + const char *type, *name; + + if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop)) + return JS_FALSE; + if (propp) { + *objp = obj2; + *propp = prop; + } + if (!prop) + return JS_TRUE; + + /* From here, return true, or goto bad on failure to drop prop. */ + if (!OBJ_GET_ATTRIBUTES(cx, obj2, id, prop, &oldAttrs)) + goto bad; + + /* If either property is readonly, we have an error. */ + report = ((oldAttrs | attrs) & JSPROP_READONLY) + ? JSREPORT_ERROR + : JSREPORT_WARNING | JSREPORT_STRICT; + + if (report != JSREPORT_ERROR) { + /* + * Allow redeclaration of variables and functions, but insist that the + * new value is not a getter if the old value was, ditto for setters -- + * unless prop is impermanent (in which case anyone could delete it and + * redefine it, willy-nilly). + */ + if (!(attrs & (JSPROP_GETTER | JSPROP_SETTER))) + return JS_TRUE; + if ((~(oldAttrs ^ attrs) & (JSPROP_GETTER | JSPROP_SETTER)) == 0) + return JS_TRUE; + if (!(oldAttrs & JSPROP_PERMANENT)) + return JS_TRUE; + report = JSREPORT_ERROR; + } + + isFunction = (oldAttrs & (JSPROP_GETTER | JSPROP_SETTER)) != 0; + if (!isFunction) { + if (!OBJ_GET_PROPERTY(cx, obj, id, &value)) + goto bad; + isFunction = JSVAL_IS_FUNCTION(cx, value); + } + type = (oldAttrs & attrs & JSPROP_GETTER) + ? js_getter_str + : (oldAttrs & attrs & JSPROP_SETTER) + ? js_setter_str + : (oldAttrs & JSPROP_READONLY) + ? js_const_str + : isFunction + ? js_function_str + : js_var_str; + name = js_AtomToPrintableString(cx, JSID_TO_ATOM(id)); + if (!name) + goto bad; + return JS_ReportErrorFlagsAndNumber(cx, report, + js_GetErrorMessage, NULL, + JSMSG_REDECLARED_VAR, + type, name); + +bad: + if (propp) { + *objp = NULL; + *propp = NULL; + } + OBJ_DROP_PROPERTY(cx, obj2, prop); + return JS_FALSE; +} + +JSBool +js_StrictlyEqual(jsval lval, jsval rval) +{ + jsval ltag = JSVAL_TAG(lval), rtag = JSVAL_TAG(rval); + jsdouble ld, rd; + + if (ltag == rtag) { + if (ltag == JSVAL_STRING) { + JSString *lstr = JSVAL_TO_STRING(lval), + *rstr = JSVAL_TO_STRING(rval); + return js_CompareStrings(lstr, rstr) == 0; + } + if (ltag == JSVAL_DOUBLE) { + ld = *JSVAL_TO_DOUBLE(lval); + rd = *JSVAL_TO_DOUBLE(rval); + return JSDOUBLE_COMPARE(ld, ==, rd, JS_FALSE); + } + return lval == rval; + } + if (ltag == JSVAL_DOUBLE && JSVAL_IS_INT(rval)) { + ld = *JSVAL_TO_DOUBLE(lval); + rd = JSVAL_TO_INT(rval); + return JSDOUBLE_COMPARE(ld, ==, rd, JS_FALSE); + } + if (JSVAL_IS_INT(lval) && rtag == JSVAL_DOUBLE) { + ld = JSVAL_TO_INT(lval); + rd = *JSVAL_TO_DOUBLE(rval); + return JSDOUBLE_COMPARE(ld, ==, rd, JS_FALSE); + } + return lval == rval; +} + +static JSBool +InternStringElementId(JSContext *cx, jsval idval, jsid *idp) +{ + JSAtom *atom; + + atom = js_ValueToStringAtom(cx, idval); + if (!atom) + return JS_FALSE; + *idp = ATOM_TO_JSID(atom); + return JS_TRUE; +} + +static JSBool +InternNonIntElementId(JSContext *cx, jsval idval, jsid *idp) +{ + JS_ASSERT(!JSVAL_IS_INT(idval)); + +#if JS_HAS_XML_SUPPORT + if (JSVAL_IS_OBJECT(idval)) { + *idp = OBJECT_JSVAL_TO_JSID(idval); + return JS_TRUE; + } +#endif + + return InternStringElementId(cx, idval, idp); +} + +#if JS_HAS_XML_SUPPORT +#define CHECK_ELEMENT_ID(obj, id) \ + JS_BEGIN_MACRO \ + if (JSID_IS_OBJECT(id) && !OBJECT_IS_XML(cx, obj)) { \ + SAVE_SP(fp); \ + ok = InternStringElementId(cx, OBJECT_JSID_TO_JSVAL(id), &id); \ + if (!ok) \ + goto out; \ + } \ + JS_END_MACRO + +#else +#define CHECK_ELEMENT_ID(obj, id) JS_ASSERT(!JSID_IS_OBJECT(id)) +#endif + +#ifndef MAX_INTERP_LEVEL +#if defined(XP_OS2) +#define MAX_INTERP_LEVEL 250 +#else +#define MAX_INTERP_LEVEL 1000 +#endif +#endif + +#define MAX_INLINE_CALL_COUNT 1000 + +JSBool +js_Interpret(JSContext *cx, jsbytecode *pc, jsval *result) +{ + JSRuntime *rt; + JSStackFrame *fp; + JSScript *script; + uintN inlineCallCount; + JSObject *obj, *obj2, *proto, *parent; + JSVersion currentVersion, originalVersion; + JSBranchCallback onbranch; + JSBool ok, cond; + JSTrapHandler interruptHandler; + jsint depth, len; + jsval *sp, *newsp; + void *mark; + jsbytecode *endpc, *pc2; + JSOp op, op2; + const JSCodeSpec *cs; + jsatomid atomIndex; + JSAtom *atom; + uintN argc, slot, attrs; + jsval *vp, lval, rval, ltmp, rtmp; + jsid id; + JSObject *withobj, *origobj, *propobj; + jsval iter_state; + JSProperty *prop; + JSScopeProperty *sprop; + JSString *str, *str2; + jsint i, j; + jsdouble d, d2; + JSClass *clasp, *funclasp; + JSFunction *fun; + JSType type; +#ifdef DEBUG + FILE *tracefp; +#endif +#if JS_HAS_EXPORT_IMPORT + JSIdArray *ida; +#endif +#if JS_HAS_SWITCH_STATEMENT + jsint low, high, off, npairs; + JSBool match; +#endif +#if JS_HAS_GETTER_SETTER + JSPropertyOp getter, setter; +#endif +#if JS_HAS_XML_SUPPORT + JSBool foreach = JS_FALSE; +#endif + int stackDummy; + + *result = JSVAL_VOID; + rt = cx->runtime; + + /* Set registerized frame pointer and derived script pointer. */ + fp = cx->fp; + script = fp->script; + + /* Count of JS function calls that nest in this C js_Interpret frame. */ + inlineCallCount = 0; + + /* + * Optimized Get and SetVersion for proper script language versioning. + * + * If any native method or JSClass/JSObjectOps hook calls js_SetVersion + * and changes cx->version, the effect will "stick" and we will stop + * maintaining currentVersion. This is relied upon by testsuites, for + * the most part -- web browsers select version before compiling and not + * at run-time. + */ + currentVersion = script->version; + originalVersion = cx->version; + if (currentVersion != originalVersion) + js_SetVersion(cx, currentVersion); + + /* + * Prepare to call a user-supplied branch handler, and abort the script + * if it returns false. We reload onbranch after calling out to native + * functions (but not to getters, setters, or other native hooks). + */ +#define LOAD_BRANCH_CALLBACK(cx) (onbranch = (cx)->branchCallback) + + LOAD_BRANCH_CALLBACK(cx); + ok = JS_TRUE; +#define CHECK_BRANCH(len) \ + JS_BEGIN_MACRO \ + if (len <= 0 && onbranch) { \ + SAVE_SP(fp); \ + if (!(ok = (*onbranch)(cx, script))) \ + goto out; \ + } \ + JS_END_MACRO + + /* + * Load the debugger's interrupt hook here and after calling out to native + * functions (but not to getters, setters, or other native hooks), so we do + * not have to reload it each time through the interpreter loop -- we hope + * the compiler can keep it in a register. + * XXX if it spills, we still lose + */ +#define LOAD_INTERRUPT_HANDLER(rt) (interruptHandler = (rt)->interruptHandler) + + LOAD_INTERRUPT_HANDLER(rt); + + /* Check for too much js_Interpret nesting, or too deep a C stack. */ + if (++cx->interpLevel == MAX_INTERP_LEVEL || + !JS_CHECK_STACK_SIZE(cx, stackDummy)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED); + ok = JS_FALSE; + goto out2; + } + + /* + * Allocate operand and pc stack slots for the script's worst-case depth, + * unless we're called to interpret a part of an already active script, a + * filtering predicate expression for example. + */ + depth = (jsint) script->depth; + if (JS_LIKELY(!fp->spbase)) { + newsp = js_AllocRawStack(cx, (uintN)(2 * depth), &mark); + if (!newsp) { + ok = JS_FALSE; + goto out2; + } + sp = newsp + depth; + fp->spbase = sp; + SAVE_SP(fp); + } else { + sp = fp->sp; + JS_ASSERT(JS_UPTRDIFF(sp, fp->spbase) <= depth * sizeof(jsval)); + newsp = fp->spbase - depth; + mark = NULL; + } + + endpc = script->code + script->length; + while (pc < endpc) { + fp->pc = pc; + op = (JSOp) *pc; + do_op: + cs = &js_CodeSpec[op]; + len = cs->length; + +#ifdef DEBUG + tracefp = (FILE *) cx->tracefp; + if (tracefp) { + intN nuses, n; + + fprintf(tracefp, "%4u: ", js_PCToLineNumber(cx, script, pc)); + js_Disassemble1(cx, script, pc, + PTRDIFF(pc, script->code, jsbytecode), JS_FALSE, + tracefp); + nuses = cs->nuses; + if (nuses) { + SAVE_SP(fp); + for (n = -nuses; n < 0; n++) { + str = js_DecompileValueGenerator(cx, n, sp[n], NULL); + if (str) { + fprintf(tracefp, "%s %s", + (n == -nuses) ? " inputs:" : ",", + JS_GetStringBytes(str)); + } + } + fprintf(tracefp, " @ %d\n", sp - fp->spbase); + } + } +#endif + + if (interruptHandler) { + SAVE_SP(fp); + switch (interruptHandler(cx, script, pc, &rval, + rt->interruptHandlerData)) { + case JSTRAP_ERROR: + ok = JS_FALSE; + goto out; + case JSTRAP_CONTINUE: + break; + case JSTRAP_RETURN: + fp->rval = rval; + goto out; +#if JS_HAS_EXCEPTIONS + case JSTRAP_THROW: + cx->throwing = JS_TRUE; + cx->exception = rval; + ok = JS_FALSE; + goto out; +#endif /* JS_HAS_EXCEPTIONS */ + default:; + } + LOAD_INTERRUPT_HANDLER(rt); + } + + switch (op) { + case JSOP_NOP: + break; + + case JSOP_GROUP: + break; + + case JSOP_PUSH: + PUSH_OPND(JSVAL_VOID); + break; + + case JSOP_POP: + sp--; + break; + + case JSOP_POP2: + sp -= 2; + break; + + case JSOP_SWAP: + /* + * N.B. JSOP_SWAP doesn't swap the corresponding generating pcs + * for the operands it swaps. + */ + ltmp = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = ltmp; + break; + + case JSOP_POPV: + *result = POP_OPND(); + break; + + case JSOP_ENTERWITH: + FETCH_OBJECT(cx, -1, rval, obj); + SAVE_SP(fp); + withobj = js_NewObject(cx, &js_WithClass, obj, fp->scopeChain); + if (!withobj) + goto out; + rval = INT_TO_JSVAL(sp - fp->spbase); + OBJ_SET_SLOT(cx, withobj, JSSLOT_PRIVATE, rval); + fp->scopeChain = withobj; + STORE_OPND(-1, OBJECT_TO_JSVAL(withobj)); + break; + + case JSOP_LEAVEWITH: + rval = POP_OPND(); + JS_ASSERT(JSVAL_IS_OBJECT(rval)); + withobj = JSVAL_TO_OBJECT(rval); + JS_ASSERT(OBJ_GET_CLASS(cx, withobj) == &js_WithClass); + + rval = OBJ_GET_SLOT(cx, withobj, JSSLOT_PARENT); + JS_ASSERT(JSVAL_IS_OBJECT(rval)); + fp->scopeChain = JSVAL_TO_OBJECT(rval); + break; + + case JSOP_SETRVAL: + fp->rval = POP_OPND(); + break; + + case JSOP_RETURN: + CHECK_BRANCH(-1); + fp->rval = POP_OPND(); + /* FALL THROUGH */ + + case JSOP_RETRVAL: /* fp->rval already set */ + if (inlineCallCount) + inline_return: + { + JSInlineFrame *ifp = (JSInlineFrame *) fp; + void *hookData = ifp->hookData; + + if (hookData) { + JSInterpreterHook hook = cx->runtime->callHook; + if (hook) { + hook(cx, fp, JS_FALSE, &ok, hookData); + LOAD_INTERRUPT_HANDLER(rt); + } + } + +#if JS_HAS_CALL_OBJECT + /* + * If frame has a call object, sync values and clear the back- + * pointer. This can happen for a lightweight function if it + * calls eval unexpectedly (in a way that is hidden from the + * compiler). See bug 325540. + */ + if (fp->callobj) + ok &= js_PutCallObject(cx, fp); +#endif +#if JS_HAS_ARGS_OBJECT + if (fp->argsobj) + ok &= js_PutArgsObject(cx, fp); +#endif + + /* Restore context version only if callee hasn't set version. */ + if (cx->version == currentVersion) { + currentVersion = ifp->callerVersion; + if (currentVersion != cx->version) + js_SetVersion(cx, currentVersion); + } + + /* Store the return value in the caller's operand frame. */ + vp = fp->argv - 2; + *vp = fp->rval; + + /* Restore cx->fp and release the inline frame's space. */ + cx->fp = fp = fp->down; + JS_ARENA_RELEASE(&cx->stackPool, ifp->mark); + + /* Restore sp to point just above the return value. */ + fp->sp = vp + 1; + RESTORE_SP(fp); + + /* Restore the calling script's interpreter registers. */ + script = fp->script; + depth = (jsint) script->depth; + pc = fp->pc; + endpc = script->code + script->length; + + /* Store the generating pc for the return value. */ + vp[-depth] = (jsval)pc; + + /* Set remaining variables for 'goto advance_pc'. */ + op = (JSOp) *pc; + cs = &js_CodeSpec[op]; + len = cs->length; + + /* Resume execution in the calling frame. */ + inlineCallCount--; + if (ok) + goto advance_pc; + } + goto out; + +#if JS_HAS_SWITCH_STATEMENT + case JSOP_DEFAULT: + (void) POP(); + /* FALL THROUGH */ +#endif + case JSOP_GOTO: + len = GET_JUMP_OFFSET(pc); + CHECK_BRANCH(len); + break; + + case JSOP_IFEQ: + POP_BOOLEAN(cx, rval, cond); + if (cond == JS_FALSE) { + len = GET_JUMP_OFFSET(pc); + CHECK_BRANCH(len); + } + break; + + case JSOP_IFNE: + POP_BOOLEAN(cx, rval, cond); + if (cond != JS_FALSE) { + len = GET_JUMP_OFFSET(pc); + CHECK_BRANCH(len); + } + break; + + case JSOP_OR: + POP_BOOLEAN(cx, rval, cond); + if (cond == JS_TRUE) { + len = GET_JUMP_OFFSET(pc); + PUSH_OPND(rval); + } + break; + + case JSOP_AND: + POP_BOOLEAN(cx, rval, cond); + if (cond == JS_FALSE) { + len = GET_JUMP_OFFSET(pc); + PUSH_OPND(rval); + } + break; + + +#if JS_HAS_SWITCH_STATEMENT + case JSOP_DEFAULTX: + (void) POP(); + /* FALL THROUGH */ +#endif + case JSOP_GOTOX: + len = GET_JUMPX_OFFSET(pc); + CHECK_BRANCH(len); + break; + + case JSOP_IFEQX: + POP_BOOLEAN(cx, rval, cond); + if (cond == JS_FALSE) { + len = GET_JUMPX_OFFSET(pc); + CHECK_BRANCH(len); + } + break; + + case JSOP_IFNEX: + POP_BOOLEAN(cx, rval, cond); + if (cond != JS_FALSE) { + len = GET_JUMPX_OFFSET(pc); + CHECK_BRANCH(len); + } + break; + + case JSOP_ORX: + POP_BOOLEAN(cx, rval, cond); + if (cond == JS_TRUE) { + len = GET_JUMPX_OFFSET(pc); + PUSH_OPND(rval); + } + break; + + case JSOP_ANDX: + POP_BOOLEAN(cx, rval, cond); + if (cond == JS_FALSE) { + len = GET_JUMPX_OFFSET(pc); + PUSH_OPND(rval); + } + break; + + case JSOP_TOOBJECT: + rval = FETCH_OPND(-1); + if (!JSVAL_IS_PRIMITIVE(rval)) { + obj = JSVAL_TO_OBJECT(rval); + } else { + SAVE_SP(fp); + ok = js_ValueToObject(cx, rval, &obj); + if (!ok) + goto out; + } + STORE_OPND(-1, OBJECT_TO_JSVAL(obj)); + break; + +/* + * If the index value at sp[n] is not an int that fits in a jsval, it could + * be an object (an XML QName, AttributeName, or AnyName), but only if we are + * compiling with JS_HAS_XML_SUPPORT. Otherwise convert the index value to a + * string atom id. + */ +#define FETCH_ELEMENT_ID(n, id) \ + JS_BEGIN_MACRO \ + jsval idval_ = FETCH_OPND(n); \ + if (JSVAL_IS_INT(idval_)) { \ + id = INT_JSVAL_TO_JSID(idval_); \ + } else { \ + SAVE_SP(fp); \ + ok = InternNonIntElementId(cx, idval_, &id); \ + if (!ok) \ + goto out; \ + } \ + JS_END_MACRO + +#if JS_HAS_IN_OPERATOR + case JSOP_IN: + SAVE_SP(fp); + rval = FETCH_OPND(-1); + if (JSVAL_IS_PRIMITIVE(rval)) { + str = js_DecompileValueGenerator(cx, -1, rval, NULL); + if (str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_IN_NOT_OBJECT, + JS_GetStringBytes(str)); + } + ok = JS_FALSE; + goto out; + } + obj = JSVAL_TO_OBJECT(rval); + FETCH_ELEMENT_ID(-2, id); + CHECK_ELEMENT_ID(obj, id); + ok = OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop); + if (!ok) + goto out; + sp--; + STORE_OPND(-1, BOOLEAN_TO_JSVAL(prop != NULL)); + if (prop) + OBJ_DROP_PROPERTY(cx, obj2, prop); + break; +#endif /* JS_HAS_IN_OPERATOR */ + + case JSOP_FORPROP: + /* + * Handle JSOP_FORPROP first, so the cost of the goto do_forinloop + * is not paid for the more common cases. + */ + lval = FETCH_OPND(-1); + atom = GET_ATOM(cx, script, pc); + id = ATOM_TO_JSID(atom); + i = -2; + goto do_forinloop; + + case JSOP_FORNAME: + atom = GET_ATOM(cx, script, pc); + id = ATOM_TO_JSID(atom); + + /* + * ECMA 12.6.3 says to eval the LHS after looking for properties + * to enumerate, and bail without LHS eval if there are no props. + * We do Find here to share the most code at label do_forinloop. + * If looking for enumerable properties could have side effects, + * then we'd have to move this into the common code and condition + * it on op == JSOP_FORNAME. + */ + SAVE_SP(fp); + ok = js_FindProperty(cx, id, &obj, &obj2, &prop); + if (!ok) + goto out; + if (prop) + OBJ_DROP_PROPERTY(cx, obj2, prop); + lval = OBJECT_TO_JSVAL(obj); + /* FALL THROUGH */ + + case JSOP_FORARG: + case JSOP_FORVAR: + /* + * JSOP_FORARG and JSOP_FORVAR don't require any lval computation + * here, because they address slots on the stack (in fp->args and + * fp->vars, respectively). + */ + /* FALL THROUGH */ + + case JSOP_FORELEM: + /* + * JSOP_FORELEM simply initializes or updates the iteration state + * and leaves the index expression evaluation and assignment to the + * enumerator until after the next property has been acquired, via + * a JSOP_ENUMELEM bytecode. + */ + i = -1; + + do_forinloop: + /* + * ECMA-compatible for/in evals the object just once, before loop. + * Bad old bytecodes (since removed) did it on every iteration. + */ + obj = JSVAL_TO_OBJECT(sp[i]); + + /* If the thing to the right of 'in' has no properties, break. */ + if (!obj) { + rval = JSVAL_FALSE; + goto end_forinloop; + } + + /* + * Save the thing to the right of 'in' as origobj. Later on, we + * use this variable to suppress enumeration of shadowed prototype + * properties. + */ + origobj = obj; + + /* + * Reach under the top of stack to find our property iterator, a + * JSObject that contains the iteration state. (An object is used + * rather than a native struct so that the iteration state is + * cleaned up via GC if the for-in loop terminates abruptly.) + */ + vp = &sp[i - 1]; + rval = *vp; + + /* + * Save sp in fp now, before any OBJ_* call-outs that might nest + * an interpreter or GC activation on this context. + */ + SAVE_SP(fp); + + /* Is this the first iteration ? */ + if (JSVAL_IS_VOID(rval)) { + /* + * Yes, create a new JSObject to hold the iterator state. + * Use NULL as the nominal parent in js_NewObject to ensure + * that we use the correct scope chain lookup to try to find the + * PropertyIterator constructor. + */ + propobj = js_NewObject(cx, &prop_iterator_class, NULL, NULL); + if (!propobj) { + ok = JS_FALSE; + goto out; + } + + /* + * Now that we've resolved the object, use the PARENT slot to + * store the object that we're iterating over. + */ + propobj->slots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(obj); + propobj->slots[JSSLOT_ITER_STATE] = JSVAL_NULL; + + /* + * Root the parent slot so we can get it even in our finalizer + * (otherwise, it would live as long as we do, but it might be + * finalized first). + */ + ok = js_AddRoot(cx, &propobj->slots[JSSLOT_PARENT], + "propobj->parent"); + if (!ok) + goto out; + + /* + * Rewrite the iterator so we know to do the next case. + * Do this before calling the enumerator, which could + * displace cx->newborn and cause GC. + */ + *vp = OBJECT_TO_JSVAL(propobj); + + ok = +#if JS_HAS_XML_SUPPORT + (foreach && OBJECT_IS_XML(cx, obj)) + ? ((JSXMLObjectOps *) obj->map->ops)->enumerateValues + (cx, obj, JSENUMERATE_INIT, &iter_state, + NULL, NULL) + : +#endif + OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &iter_state, + NULL); + if (!ok) + goto out; + + /* + * Stash private iteration state into property iterator object. + * NB: This code knows that the first slots are pre-allocated. + */ +#if JS_INITIAL_NSLOTS < 5 +#error JS_INITIAL_NSLOTS must be greater than or equal to 5. +#endif + propobj->slots[JSSLOT_ITER_STATE] = iter_state; + } else { + /* This is not the first iteration. Recover iterator state. */ + propobj = JSVAL_TO_OBJECT(rval); + JS_ASSERT(OBJ_GET_CLASS(cx, propobj) == &prop_iterator_class); + obj = JSVAL_TO_OBJECT(propobj->slots[JSSLOT_PARENT]); + iter_state = propobj->slots[JSSLOT_ITER_STATE]; + } + + enum_next_property: + { + jsid fid; + + /* Get the next jsid to be enumerated and store it in fid. */ + ok = +#if JS_HAS_XML_SUPPORT + (foreach && OBJECT_IS_XML(cx, obj)) + ? ((JSXMLObjectOps *) obj->map->ops)->enumerateValues + (cx, obj, JSENUMERATE_NEXT, &iter_state, + &fid, &rval) + : +#endif + OBJ_ENUMERATE(cx, obj, JSENUMERATE_NEXT, &iter_state, &fid); + propobj->slots[JSSLOT_ITER_STATE] = iter_state; + + /* No more jsids to iterate in obj? */ + if (iter_state == JSVAL_NULL) { + /* Enumerate the properties on obj's prototype chain. */ + obj = OBJ_GET_PROTO(cx, obj); + if (!obj) { + /* End of property list -- terminate loop. */ + rval = JSVAL_FALSE; +#if JS_HAS_XML_SUPPORT + foreach = JS_FALSE; +#endif + goto end_forinloop; + } + + ok = +#if JS_HAS_XML_SUPPORT + (foreach && OBJECT_IS_XML(cx, obj)) + ? ((JSXMLObjectOps *) obj->map->ops)->enumerateValues + (cx, obj, JSENUMERATE_INIT, &iter_state, + NULL, NULL) + : +#endif + OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &iter_state, + NULL); + + /* + * Stash private iteration state into property iterator object. + * We do this before checking 'ok' to ensure that propobj is + * in a valid state even if OBJ_ENUMERATE returned JS_FALSE. + * NB: This code knows that the first slots are pre-allocated. + */ + propobj->slots[JSSLOT_ITER_STATE] = iter_state; + if (!ok) + goto out; + + /* + * Update the iterator JSObject's parent link to refer to the + * current object. This is used in the iterator JSObject's + * finalizer. + */ + propobj->slots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(obj); + goto enum_next_property; + } + + /* Skip properties not owned by obj when looking from origobj. */ + ok = OBJ_LOOKUP_PROPERTY(cx, origobj, fid, &obj2, &prop); + if (!ok) + goto out; + if (prop) + OBJ_DROP_PROPERTY(cx, obj2, prop); + + /* + * If the id was deleted, or found in a prototype or an unrelated + * object (specifically, not in an inner object for obj), skip it. + * This means that OBJ_LOOKUP_PROPERTY implementations must return + * an object either further on the prototype chain, or related by + * the JSExtendedClass.outerObject optional hook. + */ + if (!prop) + goto enum_next_property; + if (obj != obj2) { + cond = JS_FALSE; + clasp = OBJ_GET_CLASS(cx, obj2); + if (clasp->flags & JSCLASS_IS_EXTENDED) { + JSExtendedClass *xclasp; + + xclasp = (JSExtendedClass *) clasp; + cond = xclasp->outerObject && + xclasp->outerObject(cx, obj2) == obj; + } + if (!cond) + goto enum_next_property; + } + +#if JS_HAS_XML_SUPPORT + if (foreach) { + /* Clear the local foreach flag set by our prefix bytecode. */ + foreach = JS_FALSE; + + /* If obj is not XML, we must get rval given its fid. */ + if (!OBJECT_IS_XML(cx, obj)) { + ok = OBJ_GET_PROPERTY(cx, origobj, fid, &rval); + if (!ok) + goto out; + } + } else +#endif + { + /* Make rval a string for uniformity and compatibility. */ + if (JSID_IS_ATOM(fid)) { + rval = ATOM_KEY(JSID_TO_ATOM(fid)); + } +#if JS_HAS_XML_SUPPORT + else if (JSID_IS_OBJECT(fid)) { + str = js_ValueToString(cx, OBJECT_JSID_TO_JSVAL(fid)); + if (!str) { + ok = JS_FALSE; + goto out; + } + + rval = STRING_TO_JSVAL(str); + } +#endif + else if (!JS_VERSION_IS_1_2(cx)) { + str = js_NumberToString(cx, (jsdouble) JSID_TO_INT(fid)); + if (!str) { + ok = JS_FALSE; + goto out; + } + + rval = STRING_TO_JSVAL(str); + } else { + rval = INT_JSID_TO_JSVAL(fid); + } + } + + switch (op) { + case JSOP_FORARG: + slot = GET_ARGNO(pc); + JS_ASSERT(slot < fp->fun->nargs); + fp->argv[slot] = rval; + break; + + case JSOP_FORVAR: + slot = GET_VARNO(pc); + JS_ASSERT(slot < fp->fun->nvars); + fp->vars[slot] = rval; + break; + + case JSOP_FORELEM: + /* FORELEM is not a SET operation, it's more like BINDNAME. */ + PUSH_OPND(rval); + break; + + default: + /* Convert lval to a non-null object containing id. */ + VALUE_TO_OBJECT(cx, lval, obj); + if (i + 1 < 0) + STORE_OPND(i + 1, OBJECT_TO_JSVAL(obj)); + + /* Set the variable obj[id] to refer to rval. */ + fp->flags |= JSFRAME_ASSIGNING; + ok = OBJ_SET_PROPERTY(cx, obj, id, &rval); + fp->flags &= ~JSFRAME_ASSIGNING; + if (!ok) + goto out; + break; + } + + /* Push true to keep looping through properties. */ + rval = JSVAL_TRUE; + + end_forinloop: + sp += i + 1; + PUSH_OPND(rval); + break; + } + + case JSOP_DUP: + JS_ASSERT(sp > fp->spbase); + rval = sp[-1]; + PUSH_OPND(rval); + break; + + case JSOP_DUP2: + JS_ASSERT(sp - 1 > fp->spbase); + lval = FETCH_OPND(-2); + rval = FETCH_OPND(-1); + PUSH_OPND(lval); + PUSH_OPND(rval); + break; + +#define PROPERTY_OP(n, call) \ + JS_BEGIN_MACRO \ + /* Fetch the left part and resolve it to a non-null object. */ \ + FETCH_OBJECT(cx, n, lval, obj); \ + \ + /* Get or set the property, set ok false if error, true if success. */\ + SAVE_SP(fp); \ + call; \ + if (!ok) \ + goto out; \ + JS_END_MACRO + +#define ELEMENT_OP(n, call) \ + JS_BEGIN_MACRO \ + /* Fetch the right part and resolve it to an internal id. */ \ + FETCH_ELEMENT_ID(n, id); \ + \ + /* Fetch the left part and resolve it to a non-null object. */ \ + FETCH_OBJECT(cx, n - 1, lval, obj); \ + \ + /* Ensure that id has a type suitable for use with obj. */ \ + CHECK_ELEMENT_ID(obj, id); \ + \ + /* Get or set the element, set ok false if error, true if success. */ \ + SAVE_SP(fp); \ + call; \ + if (!ok) \ + goto out; \ + JS_END_MACRO + +/* + * Direct callers, i.e. those who do not wrap CACHED_GET and CACHED_SET calls + * in PROPERTY_OP or ELEMENT_OP macro calls must SAVE_SP(fp); beforehand, just + * in case a getter or setter function is invoked. CACHED_GET and CACHED_SET + * use cx, obj, id, and rval from their caller's lexical environment. + */ +#define CACHED_GET(call) CACHED_GET_VP(call, &rval) + +#define CACHED_GET_VP(call,vp) \ + JS_BEGIN_MACRO \ + if (!OBJ_IS_NATIVE(obj)) { \ + ok = call; \ + } else { \ + JS_LOCK_OBJ(cx, obj); \ + PROPERTY_CACHE_TEST(&rt->propertyCache, obj, id, sprop); \ + if (sprop) { \ + JSScope *scope_ = OBJ_SCOPE(obj); \ + slot = (uintN)sprop->slot; \ + *(vp) = (slot != SPROP_INVALID_SLOT) \ + ? LOCKED_OBJ_GET_SLOT(obj, slot) \ + : JSVAL_VOID; \ + JS_UNLOCK_SCOPE(cx, scope_); \ + ok = SPROP_GET(cx, sprop, obj, obj, vp); \ + JS_LOCK_SCOPE(cx, scope_); \ + if (ok && SPROP_HAS_VALID_SLOT(sprop, scope_)) \ + LOCKED_OBJ_SET_SLOT(obj, slot, *(vp)); \ + JS_UNLOCK_SCOPE(cx, scope_); \ + } else { \ + JS_UNLOCK_OBJ(cx, obj); \ + ok = call; \ + /* No fill here: js_GetProperty fills the cache. */ \ + } \ + } \ + JS_END_MACRO + +#define CACHED_SET(call) \ + JS_BEGIN_MACRO \ + if (!OBJ_IS_NATIVE(obj)) { \ + ok = call; \ + } else { \ + JSScope *scope_; \ + JS_LOCK_OBJ(cx, obj); \ + PROPERTY_CACHE_TEST(&rt->propertyCache, obj, id, sprop); \ + if (sprop && \ + !(sprop->attrs & JSPROP_READONLY) && \ + (scope_ = OBJ_SCOPE(obj), !SCOPE_IS_SEALED(scope_))) { \ + JS_UNLOCK_SCOPE(cx, scope_); \ + ok = SPROP_SET(cx, sprop, obj, obj, &rval); \ + JS_LOCK_SCOPE(cx, scope_); \ + if (ok && SPROP_HAS_VALID_SLOT(sprop, scope_)) { \ + LOCKED_OBJ_SET_SLOT(obj, sprop->slot, rval); \ + GC_POKE(cx, JSVAL_NULL); /* XXX second arg ignored */ \ + } \ + JS_UNLOCK_SCOPE(cx, scope_); \ + } else { \ + JS_UNLOCK_OBJ(cx, obj); \ + ok = call; \ + /* No fill here: js_SetProperty writes through the cache. */ \ + } \ + } \ + JS_END_MACRO + +#define BEGIN_LITOPX_CASE(OP,PCOFF) \ + case OP: \ + atomIndex = GET_ATOM_INDEX(pc + PCOFF); \ + do_##OP: \ + atom = js_GetAtom(cx, &script->atomMap, atomIndex); + +#define END_LITOPX_CASE \ + break; \ + + BEGIN_LITOPX_CASE(JSOP_SETCONST, 0) + obj = fp->varobj; + rval = FETCH_OPND(-1); + SAVE_SP(fp); + ok = OBJ_DEFINE_PROPERTY(cx, obj, ATOM_TO_JSID(atom), rval, + NULL, NULL, + JSPROP_ENUMERATE | JSPROP_PERMANENT | + JSPROP_READONLY, + NULL); + if (!ok) + goto out; + STORE_OPND(-1, rval); + END_LITOPX_CASE + + BEGIN_LITOPX_CASE(JSOP_BINDNAME, 0) + SAVE_SP(fp); + obj = js_FindIdentifierBase(cx, ATOM_TO_JSID(atom)); + if (!obj) { + ok = JS_FALSE; + goto out; + } + PUSH_OPND(OBJECT_TO_JSVAL(obj)); + END_LITOPX_CASE + + case JSOP_SETNAME: + atom = GET_ATOM(cx, script, pc); + id = ATOM_TO_JSID(atom); + rval = FETCH_OPND(-1); + lval = FETCH_OPND(-2); + JS_ASSERT(!JSVAL_IS_PRIMITIVE(lval)); + obj = JSVAL_TO_OBJECT(lval); + SAVE_SP(fp); + CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval)); + if (!ok) + goto out; + sp--; + STORE_OPND(-1, rval); + obj = NULL; + break; + +#define INTEGER_OP(OP, EXTRA_CODE) \ + JS_BEGIN_MACRO \ + FETCH_INT(cx, -1, j); \ + FETCH_INT(cx, -2, i); \ + if (!ok) \ + goto out; \ + EXTRA_CODE \ + d = i OP j; \ + sp--; \ + STORE_NUMBER(cx, -1, d); \ + JS_END_MACRO + +#define BITWISE_OP(OP) INTEGER_OP(OP, (void) 0;) +#define SIGNED_SHIFT_OP(OP) INTEGER_OP(OP, j &= 31;) + + case JSOP_BITOR: + BITWISE_OP(|); + break; + + case JSOP_BITXOR: + BITWISE_OP(^); + break; + + case JSOP_BITAND: + BITWISE_OP(&); + break; + +#define RELATIONAL_OP(OP) \ + JS_BEGIN_MACRO \ + rval = FETCH_OPND(-1); \ + lval = FETCH_OPND(-2); \ + /* Optimize for two int-tagged operands (typical loop control). */ \ + if ((lval & rval) & JSVAL_INT) { \ + ltmp = lval ^ JSVAL_VOID; \ + rtmp = rval ^ JSVAL_VOID; \ + if (ltmp && rtmp) { \ + cond = JSVAL_TO_INT(lval) OP JSVAL_TO_INT(rval); \ + } else { \ + d = ltmp ? JSVAL_TO_INT(lval) : *rt->jsNaN; \ + d2 = rtmp ? JSVAL_TO_INT(rval) : *rt->jsNaN; \ + cond = JSDOUBLE_COMPARE(d, OP, d2, JS_FALSE); \ + } \ + } else { \ + VALUE_TO_PRIMITIVE(cx, lval, JSTYPE_NUMBER, &lval); \ + sp[-2] = lval; \ + VALUE_TO_PRIMITIVE(cx, rval, JSTYPE_NUMBER, &rval); \ + if (JSVAL_IS_STRING(lval) && JSVAL_IS_STRING(rval)) { \ + str = JSVAL_TO_STRING(lval); \ + str2 = JSVAL_TO_STRING(rval); \ + cond = js_CompareStrings(str, str2) OP 0; \ + } else { \ + VALUE_TO_NUMBER(cx, lval, d); \ + VALUE_TO_NUMBER(cx, rval, d2); \ + cond = JSDOUBLE_COMPARE(d, OP, d2, JS_FALSE); \ + } \ + } \ + sp--; \ + STORE_OPND(-1, BOOLEAN_TO_JSVAL(cond)); \ + JS_END_MACRO + +/* + * NB: These macros can't use JS_BEGIN_MACRO/JS_END_MACRO around their bodies + * because they begin if/else chains, so callers must not put semicolons after + * the call expressions! + */ +#if JS_HAS_XML_SUPPORT +#define XML_EQUALITY_OP(OP) \ + if ((ltmp == JSVAL_OBJECT && \ + (obj2 = JSVAL_TO_OBJECT(lval)) && \ + OBJECT_IS_XML(cx, obj2)) || \ + (rtmp == JSVAL_OBJECT && \ + (obj2 = JSVAL_TO_OBJECT(rval)) && \ + OBJECT_IS_XML(cx, obj2))) { \ + JSXMLObjectOps *ops; \ + \ + ops = (JSXMLObjectOps *) obj2->map->ops; \ + if (obj2 == JSVAL_TO_OBJECT(rval)) \ + rval = lval; \ + SAVE_SP(fp); \ + ok = ops->equality(cx, obj2, rval, &cond); \ + if (!ok) \ + goto out; \ + cond = cond OP JS_TRUE; \ + } else + +#define XML_NAME_EQUALITY_OP(OP) \ + if (ltmp == JSVAL_OBJECT && \ + (obj2 = JSVAL_TO_OBJECT(lval)) && \ + ((clasp = OBJ_GET_CLASS(cx, obj2))->flags & JSCLASS_IS_EXTENDED)) { \ + JSExtendedClass *xclasp; \ + \ + xclasp = (JSExtendedClass *) clasp; \ + SAVE_SP(fp); \ + ok = xclasp->equality(cx, obj2, rval, &cond); \ + if (!ok) \ + goto out; \ + cond = cond OP JS_TRUE; \ + } else +#else +#define XML_EQUALITY_OP(OP) /* nothing */ +#define XML_NAME_EQUALITY_OP(OP) /* nothing */ +#endif + +#define EQUALITY_OP(OP, IFNAN) \ + JS_BEGIN_MACRO \ + rval = FETCH_OPND(-1); \ + lval = FETCH_OPND(-2); \ + ltmp = JSVAL_TAG(lval); \ + rtmp = JSVAL_TAG(rval); \ + XML_EQUALITY_OP(OP) \ + if (ltmp == rtmp) { \ + if (ltmp == JSVAL_STRING) { \ + str = JSVAL_TO_STRING(lval); \ + str2 = JSVAL_TO_STRING(rval); \ + cond = js_CompareStrings(str, str2) OP 0; \ + } else if (ltmp == JSVAL_DOUBLE) { \ + d = *JSVAL_TO_DOUBLE(lval); \ + d2 = *JSVAL_TO_DOUBLE(rval); \ + cond = JSDOUBLE_COMPARE(d, OP, d2, IFNAN); \ + } else { \ + XML_NAME_EQUALITY_OP(OP) \ + /* Handle all undefined (=>NaN) and int combinations. */ \ + cond = lval OP rval; \ + } \ + } else { \ + if (JSVAL_IS_NULL(lval) || JSVAL_IS_VOID(lval)) { \ + cond = (JSVAL_IS_NULL(rval) || JSVAL_IS_VOID(rval)) OP 1; \ + } else if (JSVAL_IS_NULL(rval) || JSVAL_IS_VOID(rval)) { \ + cond = 1 OP 0; \ + } else { \ + if (ltmp == JSVAL_OBJECT) { \ + VALUE_TO_PRIMITIVE(cx, lval, JSTYPE_VOID, &sp[-2]); \ + lval = sp[-2]; \ + ltmp = JSVAL_TAG(lval); \ + } else if (rtmp == JSVAL_OBJECT) { \ + VALUE_TO_PRIMITIVE(cx, rval, JSTYPE_VOID, &sp[-1]); \ + rval = sp[-1]; \ + rtmp = JSVAL_TAG(rval); \ + } \ + if (ltmp == JSVAL_STRING && rtmp == JSVAL_STRING) { \ + str = JSVAL_TO_STRING(lval); \ + str2 = JSVAL_TO_STRING(rval); \ + cond = js_CompareStrings(str, str2) OP 0; \ + } else { \ + VALUE_TO_NUMBER(cx, lval, d); \ + VALUE_TO_NUMBER(cx, rval, d2); \ + cond = JSDOUBLE_COMPARE(d, OP, d2, IFNAN); \ + } \ + } \ + } \ + sp--; \ + STORE_OPND(-1, BOOLEAN_TO_JSVAL(cond)); \ + JS_END_MACRO + + case JSOP_EQ: + EQUALITY_OP(==, JS_FALSE); + break; + + case JSOP_NE: + EQUALITY_OP(!=, JS_TRUE); + break; + +#if !JS_BUG_FALLIBLE_EQOPS +#define NEW_EQUALITY_OP(OP) \ + JS_BEGIN_MACRO \ + rval = FETCH_OPND(-1); \ + lval = FETCH_OPND(-2); \ + cond = js_StrictlyEqual(lval, rval) OP JS_TRUE; \ + sp--; \ + STORE_OPND(-1, BOOLEAN_TO_JSVAL(cond)); \ + JS_END_MACRO + + case JSOP_NEW_EQ: + NEW_EQUALITY_OP(==); + break; + + case JSOP_NEW_NE: + NEW_EQUALITY_OP(!=); + break; + +#if JS_HAS_SWITCH_STATEMENT + case JSOP_CASE: + NEW_EQUALITY_OP(==); + (void) POP(); + if (cond) { + len = GET_JUMP_OFFSET(pc); + CHECK_BRANCH(len); + } else { + PUSH(lval); + } + break; + + case JSOP_CASEX: + NEW_EQUALITY_OP(==); + (void) POP(); + if (cond) { + len = GET_JUMPX_OFFSET(pc); + CHECK_BRANCH(len); + } else { + PUSH(lval); + } + break; +#endif + +#endif /* !JS_BUG_FALLIBLE_EQOPS */ + + case JSOP_LT: + RELATIONAL_OP(<); + break; + + case JSOP_LE: + RELATIONAL_OP(<=); + break; + + case JSOP_GT: + RELATIONAL_OP(>); + break; + + case JSOP_GE: + RELATIONAL_OP(>=); + break; + +#undef EQUALITY_OP +#undef RELATIONAL_OP + + case JSOP_LSH: + SIGNED_SHIFT_OP(<<); + break; + + case JSOP_RSH: + SIGNED_SHIFT_OP(>>); + break; + + case JSOP_URSH: + { + uint32 u; + + FETCH_INT(cx, -1, j); + FETCH_UINT(cx, -2, u); + j &= 31; + d = u >> j; + sp--; + STORE_NUMBER(cx, -1, d); + break; + } + +#undef INTEGER_OP +#undef BITWISE_OP +#undef SIGNED_SHIFT_OP + + case JSOP_ADD: + rval = FETCH_OPND(-1); + lval = FETCH_OPND(-2); +#if JS_HAS_XML_SUPPORT + if (!JSVAL_IS_PRIMITIVE(lval) && + (obj2 = JSVAL_TO_OBJECT(lval), OBJECT_IS_XML(cx, obj2)) && + VALUE_IS_XML(cx, rval)) { + JSXMLObjectOps *ops; + + ops = (JSXMLObjectOps *) obj2->map->ops; + SAVE_SP(fp); + ok = ops->concatenate(cx, obj2, rval, &rval); + if (!ok) + goto out; + sp--; + STORE_OPND(-1, rval); + break; + } +#endif + { + VALUE_TO_PRIMITIVE(cx, lval, JSTYPE_VOID, &sp[-2]); + lval = sp[-2]; + VALUE_TO_PRIMITIVE(cx, rval, JSTYPE_VOID, &sp[-1]); + rval = sp[-1]; + if ((cond = JSVAL_IS_STRING(lval)) || JSVAL_IS_STRING(rval)) { + SAVE_SP(fp); + if (cond) { + str = JSVAL_TO_STRING(lval); + ok = (str2 = js_ValueToString(cx, rval)) != NULL; + } else { + str2 = JSVAL_TO_STRING(rval); + ok = (str = js_ValueToString(cx, lval)) != NULL; + } + if (!ok) + goto out; + str = js_ConcatStrings(cx, str, str2); + if (!str) { + ok = JS_FALSE; + goto out; + } + sp--; + STORE_OPND(-1, STRING_TO_JSVAL(str)); + } else { + VALUE_TO_NUMBER(cx, lval, d); + VALUE_TO_NUMBER(cx, rval, d2); + d += d2; + sp--; + STORE_NUMBER(cx, -1, d); + } + } + break; + +#define BINARY_OP(OP) \ + JS_BEGIN_MACRO \ + FETCH_NUMBER(cx, -1, d2); \ + FETCH_NUMBER(cx, -2, d); \ + d = d OP d2; \ + sp--; \ + STORE_NUMBER(cx, -1, d); \ + JS_END_MACRO + + case JSOP_SUB: + BINARY_OP(-); + break; + + case JSOP_MUL: + BINARY_OP(*); + break; + + case JSOP_DIV: + FETCH_NUMBER(cx, -1, d2); + FETCH_NUMBER(cx, -2, d); + sp--; + if (d2 == 0) { +#if defined(XP_WIN) + /* XXX MSVC miscompiles such that (NaN == 0) */ + if (JSDOUBLE_IS_NaN(d2)) + rval = DOUBLE_TO_JSVAL(rt->jsNaN); + else +#endif + if (d == 0 || JSDOUBLE_IS_NaN(d)) + rval = DOUBLE_TO_JSVAL(rt->jsNaN); + else if ((JSDOUBLE_HI32(d) ^ JSDOUBLE_HI32(d2)) >> 31) + rval = DOUBLE_TO_JSVAL(rt->jsNegativeInfinity); + else + rval = DOUBLE_TO_JSVAL(rt->jsPositiveInfinity); + STORE_OPND(-1, rval); + } else { + d /= d2; + STORE_NUMBER(cx, -1, d); + } + break; + + case JSOP_MOD: + FETCH_NUMBER(cx, -1, d2); + FETCH_NUMBER(cx, -2, d); + sp--; + if (d2 == 0) { + STORE_OPND(-1, DOUBLE_TO_JSVAL(rt->jsNaN)); + } else { +#if defined(XP_WIN) + /* Workaround MS fmod bug where 42 % (1/0) => NaN, not 42. */ + if (!(JSDOUBLE_IS_FINITE(d) && JSDOUBLE_IS_INFINITE(d2))) +#endif + d = fmod(d, d2); + STORE_NUMBER(cx, -1, d); + } + break; + + case JSOP_NOT: + POP_BOOLEAN(cx, rval, cond); + PUSH_OPND(BOOLEAN_TO_JSVAL(!cond)); + break; + + case JSOP_BITNOT: + FETCH_INT(cx, -1, i); + d = (jsdouble) ~i; + STORE_NUMBER(cx, -1, d); + break; + + case JSOP_NEG: + FETCH_NUMBER(cx, -1, d); +#ifdef HPUX + /* + * Negation of a zero doesn't produce a negative + * zero on HPUX. Perform the operation by bit + * twiddling. + */ + JSDOUBLE_HI32(d) ^= JSDOUBLE_HI32_SIGNBIT; +#else + d = -d; +#endif + STORE_NUMBER(cx, -1, d); + break; + + case JSOP_POS: + FETCH_NUMBER(cx, -1, d); + STORE_NUMBER(cx, -1, d); + break; + + case JSOP_NEW: + /* Get immediate argc and find the constructor function. */ + argc = GET_ARGC(pc); + +#if JS_HAS_INITIALIZERS + do_new: +#endif + SAVE_SP(fp); + vp = sp - (2 + argc); + JS_ASSERT(vp >= fp->spbase); + + fun = NULL; + obj2 = NULL; + lval = *vp; + if (!JSVAL_IS_OBJECT(lval) || + (obj2 = JSVAL_TO_OBJECT(lval)) == NULL || + /* XXX clean up to avoid special cases above ObjectOps layer */ + OBJ_GET_CLASS(cx, obj2) == &js_FunctionClass || + !obj2->map->ops->construct) + { + fun = js_ValueToFunction(cx, vp, JSV2F_CONSTRUCT); + if (!fun) { + ok = JS_FALSE; + goto out; + } + } + + clasp = &js_ObjectClass; + if (!obj2) { + proto = parent = NULL; + fun = NULL; + } else { + /* + * Get the constructor prototype object for this function. + * Use the nominal |this| parameter slot, vp[1], as a local + * root to protect this prototype, in case it has no other + * strong refs. + */ + ok = OBJ_GET_PROPERTY(cx, obj2, + ATOM_TO_JSID(rt->atomState + .classPrototypeAtom), + &vp[1]); + if (!ok) + goto out; + rval = vp[1]; + proto = JSVAL_IS_OBJECT(rval) ? JSVAL_TO_OBJECT(rval) : NULL; + parent = OBJ_GET_PARENT(cx, obj2); + + if (OBJ_GET_CLASS(cx, obj2) == &js_FunctionClass) { + funclasp = ((JSFunction *)JS_GetPrivate(cx, obj2))->clasp; + if (funclasp) + clasp = funclasp; + } + } + obj = js_NewObject(cx, clasp, proto, parent); + if (!obj) { + ok = JS_FALSE; + goto out; + } + + /* Now we have an object with a constructor method; call it. */ + vp[1] = OBJECT_TO_JSVAL(obj); + ok = js_Invoke(cx, argc, JSINVOKE_CONSTRUCT); + RESTORE_SP(fp); + LOAD_BRANCH_CALLBACK(cx); + LOAD_INTERRUPT_HANDLER(rt); + if (!ok) { + cx->newborn[GCX_OBJECT] = NULL; + goto out; + } + + /* Check the return value and update obj from it. */ + rval = *vp; + if (JSVAL_IS_PRIMITIVE(rval)) { + if (fun || !JS_VERSION_IS_ECMA(cx)) { + *vp = OBJECT_TO_JSVAL(obj); + break; + } + /* native [[Construct]] returning primitive is error */ + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_NEW_RESULT, + js_ValueToPrintableString(cx, rval)); + ok = JS_FALSE; + goto out; + } + obj = JSVAL_TO_OBJECT(rval); + JS_RUNTIME_METER(rt, constructs); + break; + + case JSOP_DELNAME: + atom = GET_ATOM(cx, script, pc); + id = ATOM_TO_JSID(atom); + + SAVE_SP(fp); + ok = js_FindProperty(cx, id, &obj, &obj2, &prop); + if (!ok) + goto out; + + /* ECMA says to return true if name is undefined or inherited. */ + rval = JSVAL_TRUE; + if (prop) { + OBJ_DROP_PROPERTY(cx, obj2, prop); + ok = OBJ_DELETE_PROPERTY(cx, obj, id, &rval); + if (!ok) + goto out; + } + PUSH_OPND(rval); + break; + + case JSOP_DELPROP: + atom = GET_ATOM(cx, script, pc); + id = ATOM_TO_JSID(atom); + PROPERTY_OP(-1, ok = OBJ_DELETE_PROPERTY(cx, obj, id, &rval)); + STORE_OPND(-1, rval); + break; + + case JSOP_DELELEM: + ELEMENT_OP(-1, ok = OBJ_DELETE_PROPERTY(cx, obj, id, &rval)); + sp--; + STORE_OPND(-1, rval); + break; + + case JSOP_TYPEOF: + rval = FETCH_OPND(-1); + SAVE_SP(fp); + type = JS_TypeOfValue(cx, rval); + atom = rt->atomState.typeAtoms[type]; + STORE_OPND(-1, ATOM_KEY(atom)); + break; + + case JSOP_VOID: + (void) POP_OPND(); + PUSH_OPND(JSVAL_VOID); + break; + + case JSOP_INCNAME: + case JSOP_DECNAME: + case JSOP_NAMEINC: + case JSOP_NAMEDEC: + atom = GET_ATOM(cx, script, pc); + id = ATOM_TO_JSID(atom); + + SAVE_SP(fp); + ok = js_FindProperty(cx, id, &obj, &obj2, &prop); + if (!ok) + goto out; + if (!prop) + goto atom_not_defined; + + OBJ_DROP_PROPERTY(cx, obj2, prop); + lval = OBJECT_TO_JSVAL(obj); + i = 0; + goto do_incop; + + case JSOP_INCPROP: + case JSOP_DECPROP: + case JSOP_PROPINC: + case JSOP_PROPDEC: + atom = GET_ATOM(cx, script, pc); + id = ATOM_TO_JSID(atom); + lval = FETCH_OPND(-1); + i = -1; + goto do_incop; + + case JSOP_INCELEM: + case JSOP_DECELEM: + case JSOP_ELEMINC: + case JSOP_ELEMDEC: + FETCH_ELEMENT_ID(-1, id); + lval = FETCH_OPND(-2); + i = -2; + + do_incop: + VALUE_TO_OBJECT(cx, lval, obj); + if (i < 0) + STORE_OPND(i, OBJECT_TO_JSVAL(obj)); + CHECK_ELEMENT_ID(obj, id); + + /* The operand must contain a number. */ + SAVE_SP(fp); + CACHED_GET(OBJ_GET_PROPERTY(cx, obj, id, &rval)); + if (!ok) + goto out; + + /* The expression result goes in rtmp, the updated value in rval. */ + if (JSVAL_IS_INT(rval) && + rval != INT_TO_JSVAL(JSVAL_INT_MIN) && + rval != INT_TO_JSVAL(JSVAL_INT_MAX)) { + if (cs->format & JOF_POST) { + rtmp = rval; + (cs->format & JOF_INC) ? (rval += 2) : (rval -= 2); + } else { + (cs->format & JOF_INC) ? (rval += 2) : (rval -= 2); + rtmp = rval; + } + } else { + +/* + * Initially, rval contains the value to increment or decrement, which is not + * yet converted. As above, the expression result goes in rtmp, the updated + * value goes in rval. Our caller must set vp to point at a GC-rooted jsval + * in which we home rtmp, to protect it from GC in case the unconverted rval + * is not a number. + */ +#define NONINT_INCREMENT_OP_MIDDLE() \ + JS_BEGIN_MACRO \ + VALUE_TO_NUMBER(cx, rval, d); \ + if (cs->format & JOF_POST) { \ + rtmp = rval; \ + if (!JSVAL_IS_NUMBER(rtmp)) { \ + ok = js_NewNumberValue(cx, d, &rtmp); \ + if (!ok) \ + goto out; \ + *vp = rtmp; \ + } \ + (cs->format & JOF_INC) ? d++ : d--; \ + ok = js_NewNumberValue(cx, d, &rval); \ + } else { \ + (cs->format & JOF_INC) ? ++d : --d; \ + ok = js_NewNumberValue(cx, d, &rval); \ + rtmp = rval; \ + } \ + if (!ok) \ + goto out; \ + JS_END_MACRO + + if (cs->format & JOF_POST) { + /* + * We must push early to protect the postfix increment + * or decrement result, if converted to a jsdouble from + * a non-number value, from GC nesting in the setter. + */ + vp = sp++; + SAVE_SP(fp); + --i; + } +#ifdef __GNUC__ + else vp = NULL; /* suppress bogus gcc warnings */ +#endif + + NONINT_INCREMENT_OP_MIDDLE(); + } + + fp->flags |= JSFRAME_ASSIGNING; + CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval)); + fp->flags &= ~JSFRAME_ASSIGNING; + if (!ok) + goto out; + sp += i; + PUSH_OPND(rtmp); + break; + +/* + * NB: This macro can't use JS_BEGIN_MACRO/JS_END_MACRO around its body because + * it must break from the switch case that calls it, not from the do...while(0) + * loop created by the JS_BEGIN/END_MACRO brackets. + */ +#define FAST_INCREMENT_OP(SLOT,COUNT,BASE,PRE,OP,MINMAX) \ + slot = SLOT; \ + JS_ASSERT(slot < fp->fun->COUNT); \ + vp = fp->BASE + slot; \ + rval = *vp; \ + if (JSVAL_IS_INT(rval) && \ + rval != INT_TO_JSVAL(JSVAL_INT_##MINMAX)) { \ + PRE = rval; \ + rval OP 2; \ + *vp = rval; \ + PUSH_OPND(PRE); \ + break; \ + } \ + goto do_nonint_fast_incop; + + case JSOP_INCARG: + FAST_INCREMENT_OP(GET_ARGNO(pc), nargs, argv, rval, +=, MAX); + case JSOP_DECARG: + FAST_INCREMENT_OP(GET_ARGNO(pc), nargs, argv, rval, -=, MIN); + case JSOP_ARGINC: + FAST_INCREMENT_OP(GET_ARGNO(pc), nargs, argv, rtmp, +=, MAX); + case JSOP_ARGDEC: + FAST_INCREMENT_OP(GET_ARGNO(pc), nargs, argv, rtmp, -=, MIN); + + case JSOP_INCVAR: + FAST_INCREMENT_OP(GET_VARNO(pc), nvars, vars, rval, +=, MAX); + case JSOP_DECVAR: + FAST_INCREMENT_OP(GET_VARNO(pc), nvars, vars, rval, -=, MIN); + case JSOP_VARINC: + FAST_INCREMENT_OP(GET_VARNO(pc), nvars, vars, rtmp, +=, MAX); + case JSOP_VARDEC: + FAST_INCREMENT_OP(GET_VARNO(pc), nvars, vars, rtmp, -=, MIN); + +#undef FAST_INCREMENT_OP + + do_nonint_fast_incop: + NONINT_INCREMENT_OP_MIDDLE(); + *vp = rval; + PUSH_OPND(rtmp); + break; + +#define FAST_GLOBAL_INCREMENT_OP(SLOWOP,PRE,OP,MINMAX) \ + slot = GET_VARNO(pc); \ + JS_ASSERT(slot < fp->nvars); \ + lval = fp->vars[slot]; \ + if (JSVAL_IS_NULL(lval)) { \ + op = SLOWOP; \ + goto do_op; \ + } \ + slot = JSVAL_TO_INT(lval); \ + obj = fp->varobj; \ + rval = OBJ_GET_SLOT(cx, obj, slot); \ + if (JSVAL_IS_INT(rval) && \ + rval != INT_TO_JSVAL(JSVAL_INT_##MINMAX)) { \ + PRE = rval; \ + rval OP 2; \ + OBJ_SET_SLOT(cx, obj, slot, rval); \ + PUSH_OPND(PRE); \ + break; \ + } \ + goto do_nonint_fast_global_incop; + + case JSOP_INCGVAR: + FAST_GLOBAL_INCREMENT_OP(JSOP_INCNAME, rval, +=, MAX); + case JSOP_DECGVAR: + FAST_GLOBAL_INCREMENT_OP(JSOP_DECNAME, rval, -=, MIN); + case JSOP_GVARINC: + FAST_GLOBAL_INCREMENT_OP(JSOP_NAMEINC, rtmp, +=, MAX); + case JSOP_GVARDEC: + FAST_GLOBAL_INCREMENT_OP(JSOP_NAMEDEC, rtmp, -=, MIN); + +#undef FAST_GLOBAL_INCREMENT_OP + + do_nonint_fast_global_incop: + vp = sp++; + SAVE_SP(fp); + NONINT_INCREMENT_OP_MIDDLE(); + OBJ_SET_SLOT(cx, obj, slot, rval); + STORE_OPND(-1, rtmp); + break; + + case JSOP_GETPROP: + /* Get an immediate atom naming the property. */ + atom = GET_ATOM(cx, script, pc); + id = ATOM_TO_JSID(atom); + PROPERTY_OP(-1, CACHED_GET(OBJ_GET_PROPERTY(cx, obj, id, &rval))); + STORE_OPND(-1, rval); + break; + + case JSOP_SETPROP: + /* Pop the right-hand side into rval for OBJ_SET_PROPERTY. */ + rval = FETCH_OPND(-1); + + /* Get an immediate atom naming the property. */ + atom = GET_ATOM(cx, script, pc); + id = ATOM_TO_JSID(atom); + PROPERTY_OP(-2, CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval))); + sp--; + STORE_OPND(-1, rval); + obj = NULL; + break; + + case JSOP_GETELEM: + ELEMENT_OP(-1, CACHED_GET(OBJ_GET_PROPERTY(cx, obj, id, &rval))); + sp--; + STORE_OPND(-1, rval); + break; + + case JSOP_SETELEM: + rval = FETCH_OPND(-1); + ELEMENT_OP(-2, CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval))); + sp -= 2; + STORE_OPND(-1, rval); + obj = NULL; + break; + + case JSOP_ENUMELEM: + /* Funky: the value to set is under the [obj, id] pair. */ + FETCH_ELEMENT_ID(-1, id); + FETCH_OBJECT(cx, -2, lval, obj); + CHECK_ELEMENT_ID(obj, id); + rval = FETCH_OPND(-3); + SAVE_SP(fp); + ok = OBJ_SET_PROPERTY(cx, obj, id, &rval); + if (!ok) + goto out; + sp -= 3; + break; + +/* + * LAZY_ARGS_THISP allows the JSOP_ARGSUB bytecode to defer creation of the + * arguments object until it is truly needed. JSOP_ARGSUB optimizes away + * arguments objects when the only uses of the 'arguments' parameter are to + * fetch individual actual parameters. But if such a use were then invoked, + * e.g., arguments[i](), the 'this' parameter would and must bind to the + * caller's arguments object. So JSOP_ARGSUB sets obj to LAZY_ARGS_THISP. + */ +#define LAZY_ARGS_THISP ((JSObject *) 1) + + case JSOP_PUSHOBJ: + if (obj == LAZY_ARGS_THISP && !(obj = js_GetArgsObject(cx, fp))) { + ok = JS_FALSE; + goto out; + } + PUSH_OPND(OBJECT_TO_JSVAL(obj)); + break; + + case JSOP_CALL: + case JSOP_EVAL: + argc = GET_ARGC(pc); + vp = sp - (argc + 2); + lval = *vp; + SAVE_SP(fp); + + if (JSVAL_IS_FUNCTION(cx, lval) && + (obj = JSVAL_TO_OBJECT(lval), + fun = (JSFunction *) JS_GetPrivate(cx, obj), + fun->interpreted && + !(fun->flags & (JSFUN_HEAVYWEIGHT | JSFUN_BOUND_METHOD)) && + argc >= (uintN)(fun->nargs + fun->extra))) + /* inline_call: */ + { + uintN nframeslots, nvars; + void *newmark; + JSInlineFrame *newifp; + JSInterpreterHook hook; + + /* Restrict recursion of lightweight functions. */ + if (inlineCallCount == MAX_INLINE_CALL_COUNT) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_OVER_RECURSED); + ok = JS_FALSE; + goto out; + } + +#if JS_HAS_JIT + /* ZZZbe should do this only if interpreted often enough. */ + ok = jsjit_Compile(cx, fun); + if (!ok) + goto out; +#endif + + /* Compute the number of stack slots needed for fun. */ + nframeslots = (sizeof(JSInlineFrame) + sizeof(jsval) - 1) + / sizeof(jsval); + nvars = fun->nvars; + script = fun->u.script; + depth = (jsint) script->depth; + + /* Allocate the frame and space for vars and operands. */ + newsp = js_AllocRawStack(cx, nframeslots + nvars + 2 * depth, + &newmark); + if (!newsp) { + ok = JS_FALSE; + goto bad_inline_call; + } + newifp = (JSInlineFrame *) newsp; + newsp += nframeslots; + + /* Initialize the stack frame. */ + memset(newifp, 0, sizeof(JSInlineFrame)); + newifp->frame.script = script; + newifp->frame.fun = fun; + newifp->frame.argc = argc; + newifp->frame.argv = vp + 2; + newifp->frame.rval = JSVAL_VOID; + newifp->frame.nvars = nvars; + newifp->frame.vars = newsp; + newifp->frame.down = fp; + newifp->frame.scopeChain = OBJ_GET_PARENT(cx, obj); + newifp->mark = newmark; + + /* Compute the 'this' parameter now that argv is set. */ + ok = js_ComputeThis(cx, JSVAL_TO_OBJECT(vp[1]), &newifp->frame); + if (!ok) { + js_FreeRawStack(cx, newmark); + goto bad_inline_call; + } +#ifdef DUMP_CALL_TABLE + LogCall(cx, *vp, argc, vp + 2); +#endif + + /* Push void to initialize local variables. */ + sp = newsp; + while (nvars--) + PUSH(JSVAL_VOID); + sp += depth; + newifp->frame.spbase = sp; + SAVE_SP(&newifp->frame); + + /* Call the debugger hook if present. */ + hook = cx->runtime->callHook; + if (hook) { + newifp->hookData = hook(cx, &newifp->frame, JS_TRUE, 0, + cx->runtime->callHookData); + LOAD_INTERRUPT_HANDLER(rt); + } + + /* Switch to new version if currentVersion wasn't overridden. */ + newifp->callerVersion = cx->version; + if (cx->version == currentVersion) { + currentVersion = script->version; + if (currentVersion != cx->version) + js_SetVersion(cx, currentVersion); + } + + /* Push the frame and set interpreter registers. */ + cx->fp = fp = &newifp->frame; + pc = script->code; + endpc = pc + script->length; + inlineCallCount++; + JS_RUNTIME_METER(rt, inlineCalls); + continue; + + bad_inline_call: + script = fp->script; + depth = (jsint) script->depth; + goto out; + } + + ok = js_Invoke(cx, argc, 0); + RESTORE_SP(fp); + LOAD_BRANCH_CALLBACK(cx); + LOAD_INTERRUPT_HANDLER(rt); + if (!ok) + goto out; + JS_RUNTIME_METER(rt, nonInlineCalls); +#if JS_HAS_LVALUE_RETURN + if (cx->rval2set) { + /* + * Sneaky: use the stack depth we didn't claim in our budget, + * but that we know is there on account of [fun, this] already + * having been pushed, at a minimum (if no args). Those two + * slots have been popped and [rval] has been pushed, which + * leaves one more slot for rval2 before we might overflow. + * + * NB: rval2 must be the property identifier, and rval the + * object from which to get the property. The pair form an + * ECMA "reference type", which can be used on the right- or + * left-hand side of assignment ops. Only native methods can + * return reference types. See JSOP_SETCALL just below for + * the left-hand-side case. + */ + PUSH_OPND(cx->rval2); + cx->rval2set = JS_FALSE; + ELEMENT_OP(-1, ok = OBJ_GET_PROPERTY(cx, obj, id, &rval)); + sp--; + STORE_OPND(-1, rval); + } +#endif + obj = NULL; + break; + +#if JS_HAS_LVALUE_RETURN + case JSOP_SETCALL: + argc = GET_ARGC(pc); + SAVE_SP(fp); + ok = js_Invoke(cx, argc, 0); + RESTORE_SP(fp); + LOAD_BRANCH_CALLBACK(cx); + LOAD_INTERRUPT_HANDLER(rt); + if (!ok) + goto out; + if (!cx->rval2set) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_LEFTSIDE_OF_ASS); + ok = JS_FALSE; + goto out; + } + PUSH_OPND(cx->rval2); + cx->rval2set = JS_FALSE; + obj = NULL; + break; +#endif + + case JSOP_NAME: + atom = GET_ATOM(cx, script, pc); + id = ATOM_TO_JSID(atom); + + SAVE_SP(fp); + ok = js_FindProperty(cx, id, &obj, &obj2, &prop); + if (!ok) + goto out; + if (!prop) { + /* Kludge to allow (typeof foo == "undefined") tests. */ + for (pc2 = pc + len; pc2 < endpc; pc2++) { + op2 = (JSOp)*pc2; + if (op2 == JSOP_TYPEOF) { + PUSH_OPND(JSVAL_VOID); + goto advance_pc; + } + if (op2 != JSOP_GROUP) + break; + } + goto atom_not_defined; + } + + /* Take the slow path if prop was not found in a native object. */ + if (!OBJ_IS_NATIVE(obj) || !OBJ_IS_NATIVE(obj2)) { + OBJ_DROP_PROPERTY(cx, obj2, prop); + ok = OBJ_GET_PROPERTY(cx, obj, id, &rval); + if (!ok) + goto out; + PUSH_OPND(rval); + break; + } + + /* Get and push the obj[id] property's value. */ + sprop = (JSScopeProperty *)prop; + slot = (uintN)sprop->slot; + rval = (slot != SPROP_INVALID_SLOT) + ? LOCKED_OBJ_GET_SLOT(obj2, slot) + : JSVAL_VOID; + JS_UNLOCK_OBJ(cx, obj2); + ok = SPROP_GET(cx, sprop, obj, obj2, &rval); + JS_LOCK_OBJ(cx, obj2); + if (!ok) { + OBJ_DROP_PROPERTY(cx, obj2, prop); + goto out; + } + if (SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(obj2))) + LOCKED_OBJ_SET_SLOT(obj2, slot, rval); + OBJ_DROP_PROPERTY(cx, obj2, prop); + PUSH_OPND(rval); + break; + + case JSOP_UINT16: + i = (jsint) GET_ATOM_INDEX(pc); + rval = INT_TO_JSVAL(i); + PUSH_OPND(rval); + obj = NULL; + break; + + case JSOP_UINT24: + i = (jsint) GET_LITERAL_INDEX(pc); + rval = INT_TO_JSVAL(i); + PUSH_OPND(rval); + break; + + case JSOP_LITERAL: + atomIndex = GET_LITERAL_INDEX(pc); + atom = js_GetAtom(cx, &script->atomMap, atomIndex); + PUSH_OPND(ATOM_KEY(atom)); + obj = NULL; + break; + + case JSOP_FINDNAME: + atomIndex = GET_LITERAL_INDEX(pc); + atom = js_GetAtom(cx, &script->atomMap, atomIndex); + SAVE_SP(fp); + obj = js_FindIdentifierBase(cx, ATOM_TO_JSID(atom)); + if (!obj) { + ok = JS_FALSE; + goto out; + } + PUSH_OPND(OBJECT_TO_JSVAL(obj)); + PUSH_OPND(ATOM_KEY(atom)); + break; + + case JSOP_LITOPX: + atomIndex = GET_LITERAL_INDEX(pc); + op = pc[1 + LITERAL_INDEX_LEN]; + switch (op) { + case JSOP_ANONFUNOBJ: goto do_JSOP_ANONFUNOBJ; + case JSOP_BINDNAME: goto do_JSOP_BINDNAME; + case JSOP_CLOSURE: goto do_JSOP_CLOSURE; + case JSOP_DEFCONST: goto do_JSOP_DEFCONST; + case JSOP_DEFFUN: goto do_JSOP_DEFFUN; + case JSOP_DEFLOCALFUN: goto do_JSOP_DEFLOCALFUN; + case JSOP_DEFVAR: goto do_JSOP_DEFVAR; +#if JS_HAS_EXPORT_IMPORT + case JSOP_EXPORTNAME: goto do_JSOP_EXPORTNAME; +#endif +#if JS_HAS_XML_SUPPORT + case JSOP_GETMETHOD: goto do_JSOP_GETMETHOD; + case JSOP_SETMETHOD: goto do_JSOP_SETMETHOD; +#endif + case JSOP_INITCATCHVAR: goto do_JSOP_INITCATCHVAR; + case JSOP_NAMEDFUNOBJ: goto do_JSOP_NAMEDFUNOBJ; + case JSOP_NUMBER: goto do_JSOP_NUMBER; + case JSOP_OBJECT: goto do_JSOP_OBJECT; +#if JS_HAS_XML_SUPPORT + case JSOP_QNAMECONST: goto do_JSOP_QNAMECONST; + case JSOP_QNAMEPART: goto do_JSOP_QNAMEPART; +#endif + case JSOP_REGEXP: goto do_JSOP_REGEXP; + case JSOP_SETCONST: goto do_JSOP_SETCONST; + case JSOP_STRING: goto do_JSOP_STRING; +#if JS_HAS_XML_SUPPORT + case JSOP_XMLCDATA: goto do_JSOP_XMLCDATA; + case JSOP_XMLCOMMENT: goto do_JSOP_XMLCOMMENT; + case JSOP_XMLOBJECT: goto do_JSOP_XMLOBJECT; + case JSOP_XMLPI: goto do_JSOP_XMLPI; +#endif + default: JS_ASSERT(0); + } + /* NOTREACHED */ + break; + + case JSOP_NUMBER: + case JSOP_STRING: + case JSOP_OBJECT: + atomIndex = GET_ATOM_INDEX(pc); + + do_JSOP_NUMBER: + do_JSOP_STRING: + do_JSOP_OBJECT: + atom = js_GetAtom(cx, &script->atomMap, atomIndex); + PUSH_OPND(ATOM_KEY(atom)); + obj = NULL; + break; + + BEGIN_LITOPX_CASE(JSOP_REGEXP, 0) + { + JSRegExp *re; + JSObject *funobj; + + /* + * Push a regexp object for the atom mapped by the bytecode at pc, + * cloning the literal's regexp object if necessary, to simulate in + * the pre-compile/execute-later case what ECMA specifies for the + * compile-and-go case: that scanning each regexp literal creates + * a single corresponding RegExp object. + * + * To support pre-compilation transparently, we must handle the + * case where a regexp object literal is used in a different global + * at execution time from the global with which it was scanned at + * compile time. We do this by re-wrapping the JSRegExp private + * data struct with a cloned object having the right prototype and + * parent, and having its own lastIndex property value storage. + * + * Unlike JSOP_DEFFUN and other prolog bytecodes that may clone + * literal objects, we don't want to pay a script prolog execution + * price for all regexp literals in a script (many may not be used + * by a particular execution of that script, depending on control + * flow), so we initialize lazily here. + * + * XXX This code is specific to regular expression objects. If we + * need a similar op for other kinds of object literals, we should + * push cloning down under JSObjectOps and reuse code here. + */ + JS_ASSERT(ATOM_IS_OBJECT(atom)); + obj = ATOM_TO_OBJECT(atom); + JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_RegExpClass); + + re = (JSRegExp *) JS_GetPrivate(cx, obj); + slot = re->cloneIndex; + if (fp->fun) { + /* + * We're in function code, not global or eval code (in eval + * code, JSOP_REGEXP is never emitted). The code generator + * recorded in fp->fun->nregexps the number of re->cloneIndex + * slots that it reserved in the cloned funobj. + */ + funobj = JSVAL_TO_OBJECT(fp->argv[-2]); + slot += JSCLASS_RESERVED_SLOTS(&js_FunctionClass); + if (!JS_GetReservedSlot(cx, funobj, slot, &rval)) + return JS_FALSE; + if (JSVAL_IS_VOID(rval)) + rval = JSVAL_NULL; + } else { + /* + * We're in global code. The code generator already arranged + * via script->numGlobalVars to reserve a global variable slot + * at cloneIndex. All global variable slots are initialized + * to null, not void, for faster testing in JSOP_*GVAR cases. + */ + rval = fp->vars[slot]; +#ifdef __GNUC__ + funobj = NULL; /* suppress bogus gcc warnings */ +#endif + } + + if (JSVAL_IS_NULL(rval)) { + /* Compute the current global object in obj2. */ + obj2 = fp->scopeChain; + while ((parent = OBJ_GET_PARENT(cx, obj2)) != NULL) + obj2 = parent; + + /* + * We must home sp here, because either js_CloneRegExpObject + * or JS_SetReservedSlot could nest a last-ditch GC. We home + * pc as well, in case js_CloneRegExpObject has to lookup the + * "RegExp" class in the global object, which could entail a + * JSNewResolveOp call. + */ + SAVE_SP(fp); + + /* + * If obj's parent is not obj2, we must clone obj so that it + * has the right parent, and therefore, the right prototype. + * + * Yes, this means we assume that the correct RegExp.prototype + * to which regexp instances (including literals) delegate can + * be distinguished solely by the instance's parent, which was + * set to the parent of the RegExp constructor function object + * when the instance was created. In other words, + * + * (/x/.__parent__ == RegExp.__parent__) implies + * (/x/.__proto__ == RegExp.prototype) + * + * (unless you assign a different object to RegExp.prototype + * at runtime, in which case, ECMA doesn't specify operation, + * and you get what you deserve). + * + * This same coupling between instance parent and constructor + * parent turns up everywhere (see jsobj.c's FindConstructor, + * js_ConstructObject, and js_NewObject). It's fundamental to + * the design of the language when you consider multiple global + * objects and separate compilation and execution, even though + * it is not specified fully in ECMA. + */ + if (OBJ_GET_PARENT(cx, obj) != obj2) { + obj = js_CloneRegExpObject(cx, obj, obj2); + if (!obj) { + ok = JS_FALSE; + goto out; + } + } + rval = OBJECT_TO_JSVAL(obj); + + /* Store the regexp object value in its cloneIndex slot. */ + if (fp->fun) { + if (!JS_SetReservedSlot(cx, funobj, slot, rval)) + return JS_FALSE; + } else { + fp->vars[slot] = rval; + } + } + + PUSH_OPND(rval); + obj = NULL; + } + END_LITOPX_CASE + + case JSOP_ZERO: + PUSH_OPND(JSVAL_ZERO); + obj = NULL; + break; + + case JSOP_ONE: + PUSH_OPND(JSVAL_ONE); + obj = NULL; + break; + + case JSOP_NULL: + PUSH_OPND(JSVAL_NULL); + obj = NULL; + break; + + case JSOP_THIS: + obj = fp->thisp; + clasp = OBJ_GET_CLASS(cx, obj); + if (clasp->flags & JSCLASS_IS_EXTENDED) { + JSExtendedClass *xclasp; + + xclasp = (JSExtendedClass *) clasp; + if (xclasp->outerObject) { + obj = xclasp->outerObject(cx, obj); + if (!obj) { + ok = JS_FALSE; + goto out; + } + } + } + + PUSH_OPND(OBJECT_TO_JSVAL(obj)); + obj = NULL; + break; + + case JSOP_FALSE: + PUSH_OPND(JSVAL_FALSE); + obj = NULL; + break; + + case JSOP_TRUE: + PUSH_OPND(JSVAL_TRUE); + obj = NULL; + break; + +#if JS_HAS_SWITCH_STATEMENT + case JSOP_TABLESWITCH: + pc2 = pc; + len = GET_JUMP_OFFSET(pc2); + + /* + * ECMAv2 forbids conversion of discriminant, so we will skip to + * the default case if the discriminant isn't already an int jsval. + * (This opcode is emitted only for dense jsint-domain switches.) + */ + if ((cx->version & JSVERSION_MASK) == JSVERSION_DEFAULT || + (cx->version & JSVERSION_MASK) >= JSVERSION_1_4) { + rval = POP_OPND(); + if (!JSVAL_IS_INT(rval)) + break; + i = JSVAL_TO_INT(rval); + } else { + FETCH_INT(cx, -1, i); + sp--; + } + + pc2 += JUMP_OFFSET_LEN; + low = GET_JUMP_OFFSET(pc2); + pc2 += JUMP_OFFSET_LEN; + high = GET_JUMP_OFFSET(pc2); + + i -= low; + if ((jsuint)i < (jsuint)(high - low + 1)) { + pc2 += JUMP_OFFSET_LEN + JUMP_OFFSET_LEN * i; + off = (jsint) GET_JUMP_OFFSET(pc2); + if (off) + len = off; + } + break; + + case JSOP_LOOKUPSWITCH: + lval = POP_OPND(); + pc2 = pc; + len = GET_JUMP_OFFSET(pc2); + + if (!JSVAL_IS_NUMBER(lval) && + !JSVAL_IS_STRING(lval) && + !JSVAL_IS_BOOLEAN(lval)) { + goto advance_pc; + } + + pc2 += JUMP_OFFSET_LEN; + npairs = (jsint) GET_ATOM_INDEX(pc2); + pc2 += ATOM_INDEX_LEN; + +#define SEARCH_PAIRS(MATCH_CODE) \ + while (npairs) { \ + atom = GET_ATOM(cx, script, pc2); \ + rval = ATOM_KEY(atom); \ + MATCH_CODE \ + if (match) { \ + pc2 += ATOM_INDEX_LEN; \ + len = GET_JUMP_OFFSET(pc2); \ + goto advance_pc; \ + } \ + pc2 += ATOM_INDEX_LEN + JUMP_OFFSET_LEN; \ + npairs--; \ + } + if (JSVAL_IS_STRING(lval)) { + str = JSVAL_TO_STRING(lval); + SEARCH_PAIRS( + match = (JSVAL_IS_STRING(rval) && + ((str2 = JSVAL_TO_STRING(rval)) == str || + !js_CompareStrings(str2, str))); + ) + } else if (JSVAL_IS_DOUBLE(lval)) { + d = *JSVAL_TO_DOUBLE(lval); + SEARCH_PAIRS( + match = (JSVAL_IS_DOUBLE(rval) && + *JSVAL_TO_DOUBLE(rval) == d); + ) + } else { + SEARCH_PAIRS( + match = (lval == rval); + ) + } +#undef SEARCH_PAIRS + break; + + case JSOP_TABLESWITCHX: + pc2 = pc; + len = GET_JUMPX_OFFSET(pc2); + + /* + * ECMAv2 forbids conversion of discriminant, so we will skip to + * the default case if the discriminant isn't already an int jsval. + * (This opcode is emitted only for dense jsint-domain switches.) + */ + if ((cx->version & JSVERSION_MASK) == JSVERSION_DEFAULT || + (cx->version & JSVERSION_MASK) >= JSVERSION_1_4) { + rval = POP_OPND(); + if (!JSVAL_IS_INT(rval)) + break; + i = JSVAL_TO_INT(rval); + } else { + FETCH_INT(cx, -1, i); + sp--; + } + + pc2 += JUMPX_OFFSET_LEN; + low = GET_JUMP_OFFSET(pc2); + pc2 += JUMP_OFFSET_LEN; + high = GET_JUMP_OFFSET(pc2); + + i -= low; + if ((jsuint)i < (jsuint)(high - low + 1)) { + pc2 += JUMP_OFFSET_LEN + JUMPX_OFFSET_LEN * i; + off = (jsint) GET_JUMPX_OFFSET(pc2); + if (off) + len = off; + } + break; + + case JSOP_LOOKUPSWITCHX: + lval = POP_OPND(); + pc2 = pc; + len = GET_JUMPX_OFFSET(pc2); + + if (!JSVAL_IS_NUMBER(lval) && + !JSVAL_IS_STRING(lval) && + !JSVAL_IS_BOOLEAN(lval)) { + goto advance_pc; + } + + pc2 += JUMPX_OFFSET_LEN; + npairs = (jsint) GET_ATOM_INDEX(pc2); + pc2 += ATOM_INDEX_LEN; + +#define SEARCH_EXTENDED_PAIRS(MATCH_CODE) \ + while (npairs) { \ + atom = GET_ATOM(cx, script, pc2); \ + rval = ATOM_KEY(atom); \ + MATCH_CODE \ + if (match) { \ + pc2 += ATOM_INDEX_LEN; \ + len = GET_JUMPX_OFFSET(pc2); \ + goto advance_pc; \ + } \ + pc2 += ATOM_INDEX_LEN + JUMPX_OFFSET_LEN; \ + npairs--; \ + } + if (JSVAL_IS_STRING(lval)) { + str = JSVAL_TO_STRING(lval); + SEARCH_EXTENDED_PAIRS( + match = (JSVAL_IS_STRING(rval) && + ((str2 = JSVAL_TO_STRING(rval)) == str || + !js_CompareStrings(str2, str))); + ) + } else if (JSVAL_IS_DOUBLE(lval)) { + d = *JSVAL_TO_DOUBLE(lval); + SEARCH_EXTENDED_PAIRS( + match = (JSVAL_IS_DOUBLE(rval) && + *JSVAL_TO_DOUBLE(rval) == d); + ) + } else { + SEARCH_EXTENDED_PAIRS( + match = (lval == rval); + ) + } +#undef SEARCH_EXTENDED_PAIRS + break; + + case JSOP_CONDSWITCH: + break; + +#endif /* JS_HAS_SWITCH_STATEMENT */ + +#if JS_HAS_EXPORT_IMPORT + case JSOP_EXPORTALL: + SAVE_SP(fp); + obj = fp->varobj; + ida = JS_Enumerate(cx, obj); + if (!ida) { + ok = JS_FALSE; + } else { + for (i = 0, j = ida->length; i < j; i++) { + id = ida->vector[i]; + ok = OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop); + if (!ok) + break; + if (!prop) + continue; + ok = OBJ_GET_ATTRIBUTES(cx, obj, id, prop, &attrs); + if (ok) { + attrs |= JSPROP_EXPORTED; + ok = OBJ_SET_ATTRIBUTES(cx, obj, id, prop, &attrs); + } + OBJ_DROP_PROPERTY(cx, obj2, prop); + if (!ok) + break; + } + JS_DestroyIdArray(cx, ida); + } + break; + + BEGIN_LITOPX_CASE(JSOP_EXPORTNAME, 0) + id = ATOM_TO_JSID(atom); + obj = fp->varobj; + SAVE_SP(fp); + ok = OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop); + if (!ok) + goto out; + if (!prop) { + ok = OBJ_DEFINE_PROPERTY(cx, obj, id, JSVAL_VOID, NULL, NULL, + JSPROP_EXPORTED, NULL); + } else { + ok = OBJ_GET_ATTRIBUTES(cx, obj, id, prop, &attrs); + if (ok) { + attrs |= JSPROP_EXPORTED; + ok = OBJ_SET_ATTRIBUTES(cx, obj, id, prop, &attrs); + } + OBJ_DROP_PROPERTY(cx, obj2, prop); + } + if (!ok) + goto out; + END_LITOPX_CASE + + case JSOP_IMPORTALL: + id = (jsid) JSVAL_VOID; + PROPERTY_OP(-1, ok = ImportProperty(cx, obj, id)); + sp--; + break; + + case JSOP_IMPORTPROP: + /* Get an immediate atom naming the property. */ + atom = GET_ATOM(cx, script, pc); + id = ATOM_TO_JSID(atom); + PROPERTY_OP(-1, ok = ImportProperty(cx, obj, id)); + sp--; + break; + + case JSOP_IMPORTELEM: + ELEMENT_OP(-1, ok = ImportProperty(cx, obj, id)); + sp -= 2; + break; +#endif /* JS_HAS_EXPORT_IMPORT */ + + case JSOP_TRAP: + switch (JS_HandleTrap(cx, script, pc, &rval)) { + case JSTRAP_ERROR: + ok = JS_FALSE; + goto out; + case JSTRAP_CONTINUE: + JS_ASSERT(JSVAL_IS_INT(rval)); + op = (JSOp) JSVAL_TO_INT(rval); + JS_ASSERT((uintN)op < (uintN)JSOP_LIMIT); + LOAD_INTERRUPT_HANDLER(rt); + goto do_op; + case JSTRAP_RETURN: + fp->rval = rval; + goto out; +#if JS_HAS_EXCEPTIONS + case JSTRAP_THROW: + cx->throwing = JS_TRUE; + cx->exception = rval; + ok = JS_FALSE; + goto out; +#endif /* JS_HAS_EXCEPTIONS */ + default:; + } + LOAD_INTERRUPT_HANDLER(rt); + break; + + case JSOP_ARGUMENTS: + SAVE_SP(fp); + ok = js_GetArgsValue(cx, fp, &rval); + if (!ok) + goto out; + PUSH_OPND(rval); + break; + + case JSOP_ARGSUB: + id = INT_TO_JSID(GET_ARGNO(pc)); + SAVE_SP(fp); + ok = js_GetArgsProperty(cx, fp, id, &obj, &rval); + if (!ok) + goto out; + if (!obj) { + /* + * If arguments was not overridden by eval('arguments = ...'), + * set obj to the magic cookie respected by JSOP_PUSHOBJ, just + * in case this bytecode is part of an 'arguments[i](j, k)' or + * similar such invocation sequence, where the function that + * is invoked expects its 'this' parameter to be the caller's + * arguments object. + */ + obj = LAZY_ARGS_THISP; + } + PUSH_OPND(rval); + break; + +#undef LAZY_ARGS_THISP + + case JSOP_ARGCNT: + id = ATOM_TO_JSID(rt->atomState.lengthAtom); + SAVE_SP(fp); + ok = js_GetArgsProperty(cx, fp, id, &obj, &rval); + if (!ok) + goto out; + PUSH_OPND(rval); + break; + + case JSOP_GETARG: + slot = GET_ARGNO(pc); + JS_ASSERT(slot < fp->fun->nargs); + PUSH_OPND(fp->argv[slot]); + obj = NULL; + break; + + case JSOP_SETARG: + slot = GET_ARGNO(pc); + JS_ASSERT(slot < fp->fun->nargs); + vp = &fp->argv[slot]; + GC_POKE(cx, *vp); + *vp = FETCH_OPND(-1); + obj = NULL; + break; + + case JSOP_GETVAR: + slot = GET_VARNO(pc); + JS_ASSERT(slot < fp->fun->nvars); + PUSH_OPND(fp->vars[slot]); + obj = NULL; + break; + + case JSOP_SETVAR: + slot = GET_VARNO(pc); + JS_ASSERT(slot < fp->fun->nvars); + vp = &fp->vars[slot]; + GC_POKE(cx, *vp); + *vp = FETCH_OPND(-1); + obj = NULL; + break; + + case JSOP_GETGVAR: + slot = GET_VARNO(pc); + JS_ASSERT(slot < fp->nvars); + lval = fp->vars[slot]; + if (JSVAL_IS_NULL(lval)) { + op = JSOP_NAME; + goto do_op; + } + slot = JSVAL_TO_INT(lval); + obj = fp->varobj; + rval = OBJ_GET_SLOT(cx, obj, slot); + PUSH_OPND(rval); + break; + + case JSOP_SETGVAR: + slot = GET_VARNO(pc); + JS_ASSERT(slot < fp->nvars); + rval = FETCH_OPND(-1); + lval = fp->vars[slot]; + obj = fp->varobj; + if (JSVAL_IS_NULL(lval)) { + /* + * Inline-clone and specialize JSOP_SETNAME code here because + * JSOP_SETGVAR has arity 1: [rval], not arity 2: [obj, rval] + * as JSOP_SETNAME does, where [obj] is due to JSOP_BINDNAME. + */ + atom = GET_ATOM(cx, script, pc); + id = ATOM_TO_JSID(atom); + SAVE_SP(fp); + CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval)); + if (!ok) + goto out; + STORE_OPND(-1, rval); + } else { + slot = JSVAL_TO_INT(lval); + GC_POKE(cx, obj->slots[slot]); + OBJ_SET_SLOT(cx, obj, slot, rval); + } + obj = NULL; + break; + + case JSOP_DEFCONST: + case JSOP_DEFVAR: + atomIndex = GET_ATOM_INDEX(pc); + + do_JSOP_DEFCONST: + do_JSOP_DEFVAR: + atom = js_GetAtom(cx, &script->atomMap, atomIndex); + obj = fp->varobj; + attrs = JSPROP_ENUMERATE; + if (!(fp->flags & JSFRAME_EVAL)) + attrs |= JSPROP_PERMANENT; + if (op == JSOP_DEFCONST) + attrs |= JSPROP_READONLY; + + /* Lookup id in order to check for redeclaration problems. */ + id = ATOM_TO_JSID(atom); + SAVE_SP(fp); + ok = js_CheckRedeclaration(cx, obj, id, attrs, &obj2, &prop); + if (!ok) + goto out; + + /* Bind a variable only if it's not yet defined. */ + if (!prop) { + ok = OBJ_DEFINE_PROPERTY(cx, obj, id, JSVAL_VOID, NULL, NULL, + attrs, &prop); + if (!ok) + goto out; + JS_ASSERT(prop); + obj2 = obj; + } + + /* + * Try to optimize a property we either just created, or found + * directly in the global object, that is permanent, has a slot, + * and has stub getter and setter, into a "fast global" accessed + * by the JSOP_*GVAR opcodes. + */ + if (atomIndex < script->numGlobalVars && + (attrs & JSPROP_PERMANENT) && + obj2 == obj && + OBJ_IS_NATIVE(obj)) { + sprop = (JSScopeProperty *) prop; + if (SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(obj)) && + SPROP_HAS_STUB_GETTER(sprop) && + SPROP_HAS_STUB_SETTER(sprop)) { + /* + * Fast globals use fp->vars to map the global name's + * atomIndex to the permanent fp->varobj slot number, + * tagged as a jsval. The atomIndex for the global's + * name literal is identical to its fp->vars index. + */ + fp->vars[atomIndex] = INT_TO_JSVAL(sprop->slot); + } + } + + OBJ_DROP_PROPERTY(cx, obj2, prop); + break; + + BEGIN_LITOPX_CASE(JSOP_DEFFUN, 0) + { + uintN flags; + + atomIndex = GET_ATOM_INDEX(pc); + atom = js_GetAtom(cx, &script->atomMap, atomIndex); + obj = ATOM_TO_OBJECT(atom); + fun = (JSFunction *) JS_GetPrivate(cx, obj); + id = ATOM_TO_JSID(fun->atom); + + /* + * We must be at top-level (either outermost block that forms a + * function's body, or a global) scope, not inside an expression + * (JSOP_{ANON,NAMED}FUNOBJ) or compound statement (JSOP_CLOSURE) + * in the same compilation unit (ECMA Program). + * + * However, we could be in a Program being eval'd from inside a + * with statement, so we need to distinguish scope chain head from + * variables object. Hence the obj2 vs. parent distinction below. + * First we make sure the function object we're defining has the + * right scope chain. Then we define its name in fp->varobj. + * + * If static link is not current scope, clone fun's object to link + * to the current scope via parent. This clause exists to enable + * sharing of compiled functions among multiple equivalent scopes, + * splitting the cost of compilation evenly among the scopes and + * amortizing it over a number of executions. Examples include XUL + * scripts and event handlers shared among Mozilla chrome windows, + * and server-side JS user-defined functions shared among requests. + * + * NB: The Script object exposes compile and exec in the language, + * such that this clause introduces an incompatible change from old + * JS versions that supported Script. Such a JS version supported + * executing a script that defined and called functions scoped by + * the compile-time static link, not by the exec-time scope chain. + * + * We sacrifice compatibility, breaking such scripts, in order to + * promote compile-cost sharing and amortizing, and because Script + * is not and will not be standardized. + */ + obj2 = fp->scopeChain; + if (OBJ_GET_PARENT(cx, obj) != obj2) { + obj = js_CloneFunctionObject(cx, obj, obj2); + if (!obj) { + ok = JS_FALSE; + goto out; + } + } + + /* + * Protect obj from any GC hiding below OBJ_DEFINE_PROPERTY. All + * paths from here must flow through the "Restore fp->scopeChain" + * code below the OBJ_DEFINE_PROPERTY call. + */ + fp->scopeChain = obj; + rval = OBJECT_TO_JSVAL(obj); + + /* + * ECMA requires functions defined when entering Global code to be + * permanent, and functions defined when entering Eval code to be + * impermanent. + */ + attrs = JSPROP_ENUMERATE; + if (!(fp->flags & JSFRAME_EVAL)) + attrs |= JSPROP_PERMANENT; + + /* + * Load function flags that are also property attributes. Getters + * and setters do not need a slot, their value is stored elsewhere + * in the property itself, not in obj->slots. + */ + flags = fun->flags & (JSFUN_GETTER | JSFUN_SETTER); + if (flags) { + attrs |= flags | JSPROP_SHARED; + rval = JSVAL_VOID; + } + + /* + * Check for a const property of the same name -- or any kind + * of property if executing with the strict option. We check + * here at runtime as well as at compile-time, to handle eval + * as well as multiple HTML script tags. + */ + parent = fp->varobj; + SAVE_SP(fp); + ok = js_CheckRedeclaration(cx, parent, id, attrs, NULL, NULL); + if (ok) { + ok = OBJ_DEFINE_PROPERTY(cx, parent, id, rval, + (flags & JSFUN_GETTER) + ? (JSPropertyOp) obj + : NULL, + (flags & JSFUN_SETTER) + ? (JSPropertyOp) obj + : NULL, + attrs, + &prop); + } + + /* Restore fp->scopeChain now that obj is defined in fp->varobj. */ + fp->scopeChain = obj2; + if (!ok) + goto out; + +#if 0 + if (attrs == (JSPROP_ENUMERATE | JSPROP_PERMANENT) && + script->numGlobalVars) { + /* + * As with JSOP_DEFVAR and JSOP_DEFCONST (above), fast globals + * use fp->vars to map the global function name's atomIndex to + * its permanent fp->varobj slot number, tagged as a jsval. + */ + sprop = (JSScopeProperty *) prop; + fp->vars[atomIndex] = INT_TO_JSVAL(sprop->slot); + } +#endif + OBJ_DROP_PROPERTY(cx, parent, prop); + } + END_LITOPX_CASE + +#if JS_HAS_LEXICAL_CLOSURE + BEGIN_LITOPX_CASE(JSOP_DEFLOCALFUN, VARNO_LEN) + /* + * Define a local function (i.e., one nested at the top level of + * another function), parented by the current scope chain, and + * stored in a local variable slot that the compiler allocated. + * This is an optimization over JSOP_DEFFUN that avoids requiring + * a call object for the outer function's activation. + */ + slot = GET_VARNO(pc); + atom = js_GetAtom(cx, &script->atomMap, atomIndex); + obj = ATOM_TO_OBJECT(atom); + fun = (JSFunction *) JS_GetPrivate(cx, obj); + + parent = fp->scopeChain; + if (OBJ_GET_PARENT(cx, obj) != parent) { + SAVE_SP(fp); + obj = js_CloneFunctionObject(cx, obj, parent); + if (!obj) { + ok = JS_FALSE; + goto out; + } + } + fp->vars[slot] = OBJECT_TO_JSVAL(obj); + END_LITOPX_CASE + + BEGIN_LITOPX_CASE(JSOP_ANONFUNOBJ, 0) + /* Push the specified function object literal. */ + obj = ATOM_TO_OBJECT(atom); + + /* If re-parenting, push a clone of the function object. */ + parent = fp->scopeChain; + if (OBJ_GET_PARENT(cx, obj) != parent) { + SAVE_SP(fp); + obj = js_CloneFunctionObject(cx, obj, parent); + if (!obj) { + ok = JS_FALSE; + goto out; + } + } + PUSH_OPND(OBJECT_TO_JSVAL(obj)); + obj = NULL; + END_LITOPX_CASE + + BEGIN_LITOPX_CASE(JSOP_NAMEDFUNOBJ, 0) + /* ECMA ed. 3 FunctionExpression: function Identifier [etc.]. */ + rval = ATOM_KEY(atom); + JS_ASSERT(JSVAL_IS_FUNCTION(cx, rval)); + + /* + * 1. Create a new object as if by the expression new Object(). + * 2. Add Result(1) to the front of the scope chain. + * + * Step 2 is achieved by making the new object's parent be the + * current scope chain, and then making the new object the parent + * of the Function object clone. + */ + SAVE_SP(fp); + obj2 = fp->scopeChain; + parent = js_NewObject(cx, &js_ObjectClass, NULL, obj2); + if (!parent) { + ok = JS_FALSE; + goto out; + } + + /* + * 3. Create a new Function object as specified in section 13.2 + * with [parameters and body specified by the function expression + * that was parsed by the compiler into a Function object, and + * saved in the script's atom map]. + * + * Protect parent from GC after js_CloneFunctionObject calls into + * js_NewObject, which displaces the newborn object root in cx by + * allocating the clone, then runs a last-ditch GC while trying + * to allocate the clone's slots vector. Another, multi-threaded + * path: js_CloneFunctionObject => js_NewObject => OBJ_GET_CLASS + * which may suspend the current request in ClaimScope, with the + * newborn displaced as in the first scenario. + */ + fp->scopeChain = parent; + obj = js_CloneFunctionObject(cx, JSVAL_TO_OBJECT(rval), parent); + if (!obj) { + ok = JS_FALSE; + goto out; + } + + /* + * Protect obj from any GC hiding below OBJ_DEFINE_PROPERTY. All + * paths from here must flow through the "Restore fp->scopeChain" + * code below the OBJ_DEFINE_PROPERTY call. + */ + fp->scopeChain = obj; + rval = OBJECT_TO_JSVAL(obj); + + /* + * 4. Create a property in the object Result(1). The property's + * name is [fun->atom, the identifier parsed by the compiler], + * value is Result(3), and attributes are { DontDelete, ReadOnly }. + */ + fun = (JSFunction *) JS_GetPrivate(cx, obj); + attrs = fun->flags & (JSFUN_GETTER | JSFUN_SETTER); + if (attrs) { + attrs |= JSPROP_SHARED; + rval = JSVAL_VOID; + } + ok = OBJ_DEFINE_PROPERTY(cx, parent, ATOM_TO_JSID(fun->atom), rval, + (attrs & JSFUN_GETTER) + ? (JSPropertyOp) obj + : NULL, + (attrs & JSFUN_SETTER) + ? (JSPropertyOp) obj + : NULL, + attrs | + JSPROP_ENUMERATE | JSPROP_PERMANENT | + JSPROP_READONLY, + NULL); + + /* Restore fp->scopeChain now that obj is defined in parent. */ + fp->scopeChain = obj2; + if (!ok) { + cx->newborn[GCX_OBJECT] = NULL; + goto out; + } + + /* + * 5. Remove Result(1) from the front of the scope chain [no-op]. + * 6. Return Result(3). + */ + PUSH_OPND(OBJECT_TO_JSVAL(obj)); + obj = NULL; + END_LITOPX_CASE + + BEGIN_LITOPX_CASE(JSOP_CLOSURE, 0) + /* + * ECMA ed. 3 extension: a named function expression in a compound + * statement (not at the top statement level of global code, or at + * the top level of a function body). + * + * Get immediate operand atom, which is a function object literal. + * From it, get the function to close. + */ + JS_ASSERT(JSVAL_IS_FUNCTION(cx, ATOM_KEY(atom))); + obj = ATOM_TO_OBJECT(atom); + + /* + * Clone the function object with the current scope chain as the + * clone's parent. The original function object is the prototype + * of the clone. Do this only if re-parenting; the compiler may + * have seen the right parent already and created a sufficiently + * well-scoped function object. + */ + SAVE_SP(fp); + obj2 = fp->scopeChain; + if (OBJ_GET_PARENT(cx, obj) != obj2) { + obj = js_CloneFunctionObject(cx, obj, obj2); + if (!obj) { + ok = JS_FALSE; + goto out; + } + } + + /* + * Protect obj from any GC hiding below OBJ_DEFINE_PROPERTY. All + * paths from here must flow through the "Restore fp->scopeChain" + * code below the OBJ_DEFINE_PROPERTY call. + */ + fp->scopeChain = obj; + rval = OBJECT_TO_JSVAL(obj); + + /* + * Make a property in fp->varobj with id fun->atom and value obj, + * unless fun is a getter or setter (in which case, obj is cast to + * a JSPropertyOp and passed accordingly). + */ + fun = (JSFunction *) JS_GetPrivate(cx, obj); + attrs = fun->flags & (JSFUN_GETTER | JSFUN_SETTER); + if (attrs) { + attrs |= JSPROP_SHARED; + rval = JSVAL_VOID; + } + parent = fp->varobj; + ok = OBJ_DEFINE_PROPERTY(cx, parent, ATOM_TO_JSID(fun->atom), rval, + (attrs & JSFUN_GETTER) + ? (JSPropertyOp) obj + : NULL, + (attrs & JSFUN_SETTER) + ? (JSPropertyOp) obj + : NULL, + attrs | JSPROP_ENUMERATE + | JSPROP_PERMANENT, + &prop); + + /* Restore fp->scopeChain now that obj is defined in fp->varobj. */ + fp->scopeChain = obj2; + if (!ok) { + cx->newborn[GCX_OBJECT] = NULL; + goto out; + } + +#if 0 + if (attrs == 0 && script->numGlobalVars) { + /* + * As with JSOP_DEFVAR and JSOP_DEFCONST (above), fast globals + * use fp->vars to map the global function name's atomIndex to + * its permanent fp->varobj slot number, tagged as a jsval. + */ + sprop = (JSScopeProperty *) prop; + fp->vars[atomIndex] = INT_TO_JSVAL(sprop->slot); + } +#endif + OBJ_DROP_PROPERTY(cx, parent, prop); + END_LITOPX_CASE +#endif /* JS_HAS_LEXICAL_CLOSURE */ + +#if JS_HAS_GETTER_SETTER + case JSOP_GETTER: + case JSOP_SETTER: + JS_ASSERT(len == 1); + op2 = (JSOp) *++pc; + cs = &js_CodeSpec[op2]; + len = cs->length; + switch (op2) { + case JSOP_SETNAME: + case JSOP_SETPROP: + atom = GET_ATOM(cx, script, pc); + id = ATOM_TO_JSID(atom); + rval = FETCH_OPND(-1); + i = -1; + goto gs_pop_lval; + + case JSOP_SETELEM: + rval = FETCH_OPND(-1); + FETCH_ELEMENT_ID(-2, id); + i = -2; + gs_pop_lval: + FETCH_OBJECT(cx, i - 1, lval, obj); + break; + +#if JS_HAS_INITIALIZERS + case JSOP_INITPROP: + JS_ASSERT(sp - fp->spbase >= 2); + rval = FETCH_OPND(-1); + i = -1; + atom = GET_ATOM(cx, script, pc); + id = ATOM_TO_JSID(atom); + goto gs_get_lval; + + case JSOP_INITELEM: + JS_ASSERT(sp - fp->spbase >= 3); + rval = FETCH_OPND(-1); + FETCH_ELEMENT_ID(-2, id); + i = -2; + gs_get_lval: + lval = FETCH_OPND(i-1); + JS_ASSERT(JSVAL_IS_OBJECT(lval)); + obj = JSVAL_TO_OBJECT(lval); + break; +#endif /* JS_HAS_INITIALIZERS */ + + default: + JS_ASSERT(0); + } + + /* Ensure that id has a type suitable for use with obj. */ + CHECK_ELEMENT_ID(obj, id); + + if (JS_TypeOfValue(cx, rval) != JSTYPE_FUNCTION) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_GETTER_OR_SETTER, + (op == JSOP_GETTER) + ? js_getter_str + : js_setter_str); + ok = JS_FALSE; + goto out; + } + + /* + * Getters and setters are just like watchpoints from an access + * control point of view. + */ + SAVE_SP(fp); + ok = OBJ_CHECK_ACCESS(cx, obj, id, JSACC_WATCH, &rtmp, &attrs); + if (!ok) + goto out; + + if (op == JSOP_GETTER) { + getter = (JSPropertyOp) JSVAL_TO_OBJECT(rval); + setter = NULL; + attrs = JSPROP_GETTER; + } else { + getter = NULL; + setter = (JSPropertyOp) JSVAL_TO_OBJECT(rval); + attrs = JSPROP_SETTER; + } + attrs |= JSPROP_ENUMERATE | JSPROP_SHARED; + + /* Check for a readonly or permanent property of the same name. */ + ok = js_CheckRedeclaration(cx, obj, id, attrs, NULL, NULL); + if (!ok) + goto out; + + ok = OBJ_DEFINE_PROPERTY(cx, obj, id, JSVAL_VOID, getter, setter, + attrs, NULL); + if (!ok) + goto out; + + obj = NULL; + sp += i; + if (cs->ndefs) + STORE_OPND(-1, rval); + break; +#endif /* JS_HAS_GETTER_SETTER */ + +#if JS_HAS_INITIALIZERS + case JSOP_NEWINIT: + argc = 0; + fp->sharpDepth++; + goto do_new; + + case JSOP_ENDINIT: + if (--fp->sharpDepth == 0) + fp->sharpArray = NULL; + + /* Re-set the newborn root to the top of this object tree. */ + JS_ASSERT(sp - fp->spbase >= 1); + lval = FETCH_OPND(-1); + JS_ASSERT(JSVAL_IS_OBJECT(lval)); + cx->newborn[GCX_OBJECT] = JSVAL_TO_GCTHING(lval); + break; + + case JSOP_INITPROP: + /* Pop the property's value into rval. */ + JS_ASSERT(sp - fp->spbase >= 2); + rval = FETCH_OPND(-1); + + /* Get the immediate property name into id. */ + atom = GET_ATOM(cx, script, pc); + id = ATOM_TO_JSID(atom); + i = -1; + goto do_init; + + case JSOP_INITELEM: + /* Pop the element's value into rval. */ + JS_ASSERT(sp - fp->spbase >= 3); + rval = FETCH_OPND(-1); + + /* Pop and conditionally atomize the element id. */ + FETCH_ELEMENT_ID(-2, id); + i = -2; + + do_init: + /* Find the object being initialized at top of stack. */ + lval = FETCH_OPND(i-1); + JS_ASSERT(JSVAL_IS_OBJECT(lval)); + obj = JSVAL_TO_OBJECT(lval); + + /* Ensure that id has a type suitable for use with obj. */ + CHECK_ELEMENT_ID(obj, id); + + /* Set the property named by obj[id] to rval. */ + SAVE_SP(fp); + ok = OBJ_SET_PROPERTY(cx, obj, id, &rval); + if (!ok) + goto out; + sp += i; + break; + +#if JS_HAS_SHARP_VARS + case JSOP_DEFSHARP: + SAVE_SP(fp); + obj = fp->sharpArray; + if (!obj) { + obj = js_NewArrayObject(cx, 0, NULL); + if (!obj) { + ok = JS_FALSE; + goto out; + } + fp->sharpArray = obj; + } + i = (jsint) GET_ATOM_INDEX(pc); + id = INT_TO_JSID(i); + rval = FETCH_OPND(-1); + if (JSVAL_IS_PRIMITIVE(rval)) { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%u", (unsigned) i); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_SHARP_DEF, numBuf); + ok = JS_FALSE; + goto out; + } + ok = OBJ_SET_PROPERTY(cx, obj, id, &rval); + if (!ok) + goto out; + break; + + case JSOP_USESHARP: + i = (jsint) GET_ATOM_INDEX(pc); + id = INT_TO_JSID(i); + obj = fp->sharpArray; + if (!obj) { + rval = JSVAL_VOID; + } else { + SAVE_SP(fp); + ok = OBJ_GET_PROPERTY(cx, obj, id, &rval); + if (!ok) + goto out; + } + if (!JSVAL_IS_OBJECT(rval)) { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%u", (unsigned) i); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_SHARP_USE, numBuf); + ok = JS_FALSE; + goto out; + } + PUSH_OPND(rval); + break; +#endif /* JS_HAS_SHARP_VARS */ +#endif /* JS_HAS_INITIALIZERS */ + +#if JS_HAS_EXCEPTIONS + /* No-ops for ease of decompilation and jit'ing. */ + case JSOP_TRY: + case JSOP_FINALLY: + break; + + /* Reset the stack to the given depth. */ + case JSOP_SETSP: + i = (jsint) GET_ATOM_INDEX(pc); + JS_ASSERT(i >= 0); + sp = fp->spbase + i; + + obj = fp->scopeChain; + while (OBJ_GET_CLASS(cx, obj) == &js_WithClass && + JSVAL_TO_INT(OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE)) > i) { + obj = OBJ_GET_PARENT(cx, obj); + } + fp->scopeChain = obj; + break; + + case JSOP_GOSUB: + i = PTRDIFF(pc, script->main, jsbytecode) + len; + len = GET_JUMP_OFFSET(pc); + PUSH(INT_TO_JSVAL(i)); + break; + + case JSOP_GOSUBX: + i = PTRDIFF(pc, script->main, jsbytecode) + len; + len = GET_JUMPX_OFFSET(pc); + PUSH(INT_TO_JSVAL(i)); + break; + + case JSOP_RETSUB: + rval = POP(); + JS_ASSERT(JSVAL_IS_INT(rval)); + i = JSVAL_TO_INT(rval); + pc = script->main + i; + len = 0; + break; + + case JSOP_EXCEPTION: + PUSH(cx->exception); + cx->throwing = JS_FALSE; + break; + + case JSOP_THROW: + cx->throwing = JS_TRUE; + cx->exception = POP_OPND(); + ok = JS_FALSE; + /* let the code at out try to catch the exception. */ + goto out; + + BEGIN_LITOPX_CASE(JSOP_INITCATCHVAR, 0) + /* Load the value into rval, while keeping it live on stack. */ + JS_ASSERT(sp - fp->spbase >= 2); + rval = FETCH_OPND(-1); + + /* Get the immediate catch variable name into id. */ + id = ATOM_TO_JSID(atom); + + /* Find the object being initialized at top of stack. */ + lval = FETCH_OPND(-2); + JS_ASSERT(JSVAL_IS_OBJECT(lval)); + obj = JSVAL_TO_OBJECT(lval); + + SAVE_SP(fp); + + /* + * It's possible for an evil script to substitute a random object + * for the new object. Check to make sure that we don't override a + * readonly property with the below OBJ_DEFINE_PROPERTY. + */ + ok = OBJ_GET_ATTRIBUTES(cx, obj, id, NULL, &attrs); + if (!ok) + goto out; + if (!(attrs & (JSPROP_READONLY | JSPROP_PERMANENT | + JSPROP_GETTER | JSPROP_SETTER))) { + /* Define obj[id] to contain rval and to be permanent. */ + ok = OBJ_DEFINE_PROPERTY(cx, obj, id, rval, NULL, NULL, + JSPROP_PERMANENT, NULL); + if (!ok) + goto out; + } + + /* Now that we're done with rval, pop it. */ + sp--; + END_LITOPX_CASE +#endif /* JS_HAS_EXCEPTIONS */ + +#if JS_HAS_INSTANCEOF + case JSOP_INSTANCEOF: + rval = FETCH_OPND(-1); + if (JSVAL_IS_PRIMITIVE(rval) || + !(obj = JSVAL_TO_OBJECT(rval))->map->ops->hasInstance) { + SAVE_SP(fp); + str = js_DecompileValueGenerator(cx, -1, rval, NULL); + if (str) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_INSTANCEOF_RHS, + JS_GetStringBytes(str)); + } + ok = JS_FALSE; + goto out; + } + lval = FETCH_OPND(-2); + cond = JS_FALSE; + SAVE_SP(fp); + ok = obj->map->ops->hasInstance(cx, obj, lval, &cond); + if (!ok) + goto out; + sp--; + STORE_OPND(-1, BOOLEAN_TO_JSVAL(cond)); + break; +#endif /* JS_HAS_INSTANCEOF */ + +#if JS_HAS_DEBUGGER_KEYWORD + case JSOP_DEBUGGER: + { + JSTrapHandler handler = rt->debuggerHandler; + if (handler) { + SAVE_SP(fp); + switch (handler(cx, script, pc, &rval, + rt->debuggerHandlerData)) { + case JSTRAP_ERROR: + ok = JS_FALSE; + goto out; + case JSTRAP_CONTINUE: + break; + case JSTRAP_RETURN: + fp->rval = rval; + goto out; +#if JS_HAS_EXCEPTIONS + case JSTRAP_THROW: + cx->throwing = JS_TRUE; + cx->exception = rval; + ok = JS_FALSE; + goto out; +#endif /* JS_HAS_EXCEPTIONS */ + default:; + } + LOAD_INTERRUPT_HANDLER(rt); + } + break; + } +#endif /* JS_HAS_DEBUGGER_KEYWORD */ + +#if JS_HAS_XML_SUPPORT + case JSOP_DEFXMLNS: + rval = POP(); + SAVE_SP(fp); + ok = js_SetDefaultXMLNamespace(cx, rval); + if (!ok) + goto out; + break; + + case JSOP_ANYNAME: + SAVE_SP(fp); + ok = js_GetAnyName(cx, &rval); + if (!ok) + goto out; + PUSH_OPND(rval); + break; + + BEGIN_LITOPX_CASE(JSOP_QNAMEPART, 0) + PUSH_OPND(ATOM_KEY(atom)); + END_LITOPX_CASE + + BEGIN_LITOPX_CASE(JSOP_QNAMECONST, 0) + rval = ATOM_KEY(atom); + lval = FETCH_OPND(-1); + SAVE_SP(fp); + obj = js_ConstructXMLQNameObject(cx, lval, rval); + if (!obj) { + ok = JS_FALSE; + goto out; + } + STORE_OPND(-1, OBJECT_TO_JSVAL(obj)); + END_LITOPX_CASE + + case JSOP_QNAME: + rval = FETCH_OPND(-1); + lval = FETCH_OPND(-2); + SAVE_SP(fp); + obj = js_ConstructXMLQNameObject(cx, lval, rval); + if (!obj) { + ok = JS_FALSE; + goto out; + } + sp--; + STORE_OPND(-1, OBJECT_TO_JSVAL(obj)); + break; + + case JSOP_TOATTRNAME: + rval = FETCH_OPND(-1); + SAVE_SP(fp); + ok = js_ToAttributeName(cx, &rval); + if (!ok) + goto out; + STORE_OPND(-1, rval); + break; + + case JSOP_TOATTRVAL: + rval = FETCH_OPND(-1); + JS_ASSERT(JSVAL_IS_STRING(rval)); + SAVE_SP(fp); + str = js_EscapeAttributeValue(cx, JSVAL_TO_STRING(rval)); + if (!str) { + ok = JS_FALSE; + goto out; + } + STORE_OPND(-1, STRING_TO_JSVAL(str)); + break; + + case JSOP_ADDATTRNAME: + case JSOP_ADDATTRVAL: + rval = FETCH_OPND(-1); + lval = FETCH_OPND(-2); + str = JSVAL_TO_STRING(lval); + str2 = JSVAL_TO_STRING(rval); + SAVE_SP(fp); + str = js_AddAttributePart(cx, op == JSOP_ADDATTRNAME, str, str2); + if (!str) { + ok = JS_FALSE; + goto out; + } + sp--; + STORE_OPND(-1, STRING_TO_JSVAL(str)); + break; + + case JSOP_BINDXMLNAME: + lval = FETCH_OPND(-1); + SAVE_SP(fp); + ok = js_FindXMLProperty(cx, lval, &obj, &rval); + if (!ok) + goto out; + STORE_OPND(-1, OBJECT_TO_JSVAL(obj)); + PUSH_OPND(rval); + break; + + case JSOP_SETXMLNAME: + obj = JSVAL_TO_OBJECT(FETCH_OPND(-3)); + lval = FETCH_OPND(-2); + rval = FETCH_OPND(-1); + SAVE_SP(fp); + ok = js_SetXMLProperty(cx, obj, lval, &rval); + if (!ok) + goto out; + sp -= 2; + STORE_OPND(-1, rval); + obj = NULL; + break; + + case JSOP_XMLNAME: + lval = FETCH_OPND(-1); + SAVE_SP(fp); + ok = js_FindXMLProperty(cx, lval, &obj, &rval); + if (!ok) + goto out; + ok = js_GetXMLProperty(cx, obj, rval, &rval); + if (!ok) + goto out; + STORE_OPND(-1, rval); + break; + + case JSOP_DESCENDANTS: + case JSOP_DELDESC: + FETCH_OBJECT(cx, -2, lval, obj); + rval = FETCH_OPND(-1); + SAVE_SP(fp); + ok = js_GetXMLDescendants(cx, obj, rval, &rval); + if (!ok) + goto out; + + if (op == JSOP_DELDESC) { + sp[-1] = rval; /* set local root */ + ok = js_DeleteXMLListElements(cx, JSVAL_TO_OBJECT(rval)); + if (!ok) + goto out; + rval = JSVAL_TRUE; /* always succeed */ + } + + sp--; + STORE_OPND(-1, rval); + break; + + case JSOP_FILTER: + FETCH_OBJECT(cx, -1, lval, obj); + len = GET_JUMP_OFFSET(pc); + SAVE_SP(fp); + ok = js_FilterXMLList(cx, obj, pc + cs->length, &rval); + if (!ok) + goto out; + JS_ASSERT(fp->sp == sp); + STORE_OPND(-1, rval); + break; + + case JSOP_ENDFILTER: + *result = POP_OPND(); + goto out; + + case JSOP_STARTXML: + case JSOP_STARTXMLEXPR: + break; + + case JSOP_TOXML: + rval = FETCH_OPND(-1); + SAVE_SP(fp); + obj = js_ValueToXMLObject(cx, rval); + if (!obj) { + ok = JS_FALSE; + goto out; + } + STORE_OPND(-1, OBJECT_TO_JSVAL(obj)); + break; + + case JSOP_TOXMLLIST: + rval = FETCH_OPND(-1); + SAVE_SP(fp); + obj = js_ValueToXMLListObject(cx, rval); + if (!obj) { + ok = JS_FALSE; + goto out; + } + STORE_OPND(-1, OBJECT_TO_JSVAL(obj)); + break; + + case JSOP_XMLTAGEXPR: + rval = FETCH_OPND(-1); + SAVE_SP(fp); + str = js_ValueToString(cx, rval); + if (!str) { + ok = JS_FALSE; + goto out; + } + STORE_OPND(-1, STRING_TO_JSVAL(str)); + break; + + case JSOP_XMLELTEXPR: + rval = FETCH_OPND(-1); + SAVE_SP(fp); + if (VALUE_IS_XML(cx, rval)) { + str = js_ValueToXMLString(cx, rval); + } else { + str = js_ValueToString(cx, rval); + if (str) + str = js_EscapeElementValue(cx, str); + } + if (!str) { + ok = JS_FALSE; + goto out; + } + STORE_OPND(-1, STRING_TO_JSVAL(str)); + break; + + BEGIN_LITOPX_CASE(JSOP_XMLOBJECT, 0) + SAVE_SP(fp); + obj = js_CloneXMLObject(cx, ATOM_TO_OBJECT(atom)); + if (!obj) { + ok = JS_FALSE; + goto out; + } + PUSH_OPND(OBJECT_TO_JSVAL(obj)); + obj = NULL; + END_LITOPX_CASE + + BEGIN_LITOPX_CASE(JSOP_XMLCDATA, 0) + str = ATOM_TO_STRING(atom); + obj = js_NewXMLSpecialObject(cx, JSXML_CLASS_TEXT, NULL, str); + if (!obj) { + ok = JS_FALSE; + goto out; + } + PUSH_OPND(OBJECT_TO_JSVAL(obj)); + END_LITOPX_CASE + + BEGIN_LITOPX_CASE(JSOP_XMLCOMMENT, 0) + str = ATOM_TO_STRING(atom); + obj = js_NewXMLSpecialObject(cx, JSXML_CLASS_COMMENT, NULL, str); + if (!obj) { + ok = JS_FALSE; + goto out; + } + PUSH_OPND(OBJECT_TO_JSVAL(obj)); + END_LITOPX_CASE + + BEGIN_LITOPX_CASE(JSOP_XMLPI, 0) + str = ATOM_TO_STRING(atom); + rval = FETCH_OPND(-1); + str2 = JSVAL_TO_STRING(rval); + SAVE_SP(fp); + obj = js_NewXMLSpecialObject(cx, + JSXML_CLASS_PROCESSING_INSTRUCTION, + str, str2); + if (!obj) { + ok = JS_FALSE; + goto out; + } + STORE_OPND(-1, OBJECT_TO_JSVAL(obj)); + END_LITOPX_CASE + + BEGIN_LITOPX_CASE(JSOP_GETMETHOD, 0) + /* Get an immediate atom naming the property. */ + id = ATOM_TO_JSID(atom); + FETCH_OBJECT(cx, -1, lval, obj); + SAVE_SP(fp); + + /* Special-case XML object method lookup, per ECMA-357. */ + if (OBJECT_IS_XML(cx, obj)) { + JSXMLObjectOps *ops; + + ops = (JSXMLObjectOps *) obj->map->ops; + obj = ops->getMethod(cx, obj, id, &rval); + if (!obj) + ok = JS_FALSE; + } else { + CACHED_GET(OBJ_GET_PROPERTY(cx, obj, id, &rval)); + } + if (!ok) + goto out; + STORE_OPND(-1, rval); + END_LITOPX_CASE + + BEGIN_LITOPX_CASE(JSOP_SETMETHOD, 0) + /* Get an immediate atom naming the property. */ + id = ATOM_TO_JSID(atom); + rval = FETCH_OPND(-1); + FETCH_OBJECT(cx, -2, lval, obj); + SAVE_SP(fp); + + /* Special-case XML object method lookup, per ECMA-357. */ + if (OBJECT_IS_XML(cx, obj)) { + JSXMLObjectOps *ops; + + ops = (JSXMLObjectOps *) obj->map->ops; + ok = ops->setMethod(cx, obj, id, &rval); + } else { + CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval)); + } + if (!ok) + goto out; + --sp; + STORE_OPND(-1, rval); + obj = NULL; + END_LITOPX_CASE + + case JSOP_GETFUNNS: + ok = js_GetFunctionNamespace(cx, &rval); + if (!ok) + goto out; + PUSH_OPND(rval); + break; + + case JSOP_FOREACH: + foreach = JS_TRUE; + break; +#endif /* JS_HAS_XML_SUPPORT */ + + default: { + char numBuf[12]; + JS_snprintf(numBuf, sizeof numBuf, "%d", op); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_BYTECODE, numBuf); + ok = JS_FALSE; + goto out; + } + } + + advance_pc: + pc += len; + +#ifdef DEBUG + if (tracefp) { + intN ndefs, n; + jsval *siter; + + ndefs = cs->ndefs; + if (ndefs) { + SAVE_SP(fp); + if (op == JSOP_FORELEM && sp[-1] == JSVAL_FALSE) + --ndefs; + for (n = -ndefs; n < 0; n++) { + str = js_DecompileValueGenerator(cx, n, sp[n], NULL); + if (str) { + fprintf(tracefp, "%s %s", + (n == -ndefs) ? " output:" : ",", + JS_GetStringBytes(str)); + } + } + fprintf(tracefp, " @ %d\n", sp - fp->spbase); + } + fprintf(tracefp, " stack: "); + for (siter = fp->spbase; siter < sp; siter++) { + str = js_ValueToSource(cx, *siter); + fprintf(tracefp, "%s ", + str ? JS_GetStringBytes(str) : ""); + } + fputc('\n', tracefp); + } +#endif + } +out: + +#if JS_HAS_EXCEPTIONS + if (!ok) { + /* + * Has an exception been raised? Also insist that we are in the + * interpreter activation that pushed fp's operand stack, to avoid + * catching exceptions within XML filtering predicate expressions, + * such as the one from tests/e4x/Regress/regress-301596.js: + * + * try { + * .(@a == 1); + * throw 5; + * } catch (e) { + * } + * + * The inner interpreter activation executing the predicate bytecode + * will throw "reference to undefined XML name @a" (or 5, in older + * versions that followed the first edition of ECMA-357 and evaluated + * unbound identifiers to undefined), and the exception must not be + * caught until control unwinds to the outer interpreter activation. + * + * Otherwise, the wrong stack depth will be restored by JSOP_SETSP, + * and the catch will move into the filtering predicate expression, + * leading to double catch execution if it rethrows. + * + * XXX This assumes the null mark case implies XML filtering predicate + * expression execution! + * FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=309894 + */ + if (cx->throwing && JS_LIKELY(mark != NULL)) { + /* + * Call debugger throw hook if set (XXX thread safety?). + */ + JSTrapHandler handler = rt->throwHook; + if (handler) { + SAVE_SP(fp); + switch (handler(cx, script, pc, &rval, rt->throwHookData)) { + case JSTRAP_ERROR: + cx->throwing = JS_FALSE; + goto no_catch; + case JSTRAP_RETURN: + ok = JS_TRUE; + cx->throwing = JS_FALSE; + fp->rval = rval; + goto no_catch; + case JSTRAP_THROW: + cx->exception = rval; + case JSTRAP_CONTINUE: + default:; + } + LOAD_INTERRUPT_HANDLER(rt); + } + + /* + * Look for a try block in script that can catch this exception. + */ + SCRIPT_FIND_CATCH_START(script, pc, pc); + if (pc) { + /* Don't clear cx->throwing to save cx->exception from GC. */ + len = 0; + ok = JS_TRUE; +#if JS_HAS_XML_SUPPORT + foreach = JS_FALSE; +#endif + goto advance_pc; + } + } +no_catch:; + } +#endif + + /* + * Check whether control fell off the end of a lightweight function, or an + * exception thrown under such a function was not caught by it. If so, go + * to the inline code under JSOP_RETURN. + */ + if (inlineCallCount) { +#if JS_HAS_XML_SUPPORT + foreach = JS_FALSE; +#endif + goto inline_return; + } + + /* + * Reset sp before freeing stack slots, because our caller may GC soon. + * Clear spbase to indicate that we've popped the 2 * depth operand slots. + * Restore the previous frame's execution state. + */ + if (JS_LIKELY(mark != NULL)) { + fp->sp = fp->spbase; + fp->spbase = NULL; + js_FreeRawStack(cx, mark); + } else { + SAVE_SP(fp); + } + +out2: + if (cx->version == currentVersion && currentVersion != originalVersion) + js_SetVersion(cx, originalVersion); + cx->interpLevel--; + return ok; + +atom_not_defined: + { + const char *printable = js_AtomToPrintableString(cx, atom); + if (printable) + js_ReportIsNotDefined(cx, printable); + ok = JS_FALSE; + goto out; + } +} Index: ossp-pkg/js/src/jsinterp.c RCS File: /v/ossp/cvs/ossp-pkg/js/src/jsinterp.c,v rcsdiff -q -kk '-r1.1' '-r1.1.1.1' -u '/v/ossp/cvs/ossp-pkg/js/src/jsinterp.c,v' 2>/dev/null