RetroZilla/js/src/jsiter.c
2015-10-20 23:03:22 -04:00

1082 lines
32 KiB
C

/* -*- 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 iterators.
*/
#include "jsstddef.h"
#include <string.h> /* for memcpy */
#include "jstypes.h"
#include "jsutil.h"
#include "jsarena.h"
#include "jsapi.h"
#include "jsarray.h"
#include "jsatom.h"
#include "jsbool.h"
#include "jscntxt.h"
#include "jsconfig.h"
#include "jsexn.h"
#include "jsfun.h"
#include "jsgc.h"
#include "jsinterp.h"
#include "jsiter.h"
#include "jslock.h"
#include "jsnum.h"
#include "jsobj.h"
#include "jsopcode.h"
#include "jsscope.h"
#include "jsscript.h"
#if JS_HAS_XML_SUPPORT
#include "jsxml.h"
#endif
extern const char js_throw_str[]; /* from jsscan.h */
#define JSSLOT_ITER_STATE (JSSLOT_PRIVATE)
#define JSSLOT_ITER_FLAGS (JSSLOT_PRIVATE + 1)
#if JSSLOT_ITER_FLAGS >= JS_INITIAL_NSLOTS
#error JS_INITIAL_NSLOTS must be greater than JSSLOT_ITER_FLAGS.
#endif
/*
* Shared code to close iterator's state either through an explicit call or
* when GC detects that the iterator is no longer reachable.
*/
void
js_CloseIteratorState(JSContext *cx, JSObject *iterobj)
{
jsval *slots;
jsval state, parent;
JSObject *iterable;
JS_ASSERT(JS_InstanceOf(cx, iterobj, &js_IteratorClass, NULL));
slots = iterobj->slots;
/* Avoid double work if js_CloseNativeIterator was called on obj. */
state = slots[JSSLOT_ITER_STATE];
if (JSVAL_IS_NULL(state))
return;
/* Protect against failure to fully initialize obj. */
parent = slots[JSSLOT_PARENT];
if (!JSVAL_IS_PRIMITIVE(parent)) {
iterable = JSVAL_TO_OBJECT(parent);
#if JS_HAS_XML_SUPPORT
if ((JSVAL_TO_INT(slots[JSSLOT_ITER_FLAGS]) & JSITER_FOREACH) &&
OBJECT_IS_XML(cx, iterable)) {
((JSXMLObjectOps *) iterable->map->ops)->
enumerateValues(cx, iterable, JSENUMERATE_DESTROY, &state,
NULL, NULL);
} else
#endif
OBJ_ENUMERATE(cx, iterable, JSENUMERATE_DESTROY, &state, NULL);
}
slots[JSSLOT_ITER_STATE] = JSVAL_NULL;
}
JSClass js_IteratorClass = {
"Iterator",
JSCLASS_HAS_RESERVED_SLOTS(2) | /* slots for state and flags */
JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator),
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS
};
static JSBool
InitNativeIterator(JSContext *cx, JSObject *iterobj, JSObject *obj, uintN flags)
{
jsval state;
JSBool ok;
JS_ASSERT(JSVAL_TO_PRIVATE(iterobj->slots[JSSLOT_CLASS]) ==
&js_IteratorClass);
/* Initialize iterobj in case of enumerate hook failure. */
iterobj->slots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(obj);
iterobj->slots[JSSLOT_ITER_STATE] = JSVAL_NULL;
iterobj->slots[JSSLOT_ITER_FLAGS] = INT_TO_JSVAL(flags);
if (!js_RegisterCloseableIterator(cx, iterobj))
return JS_FALSE;
if (!obj)
return JS_TRUE;
ok =
#if JS_HAS_XML_SUPPORT
((flags & JSITER_FOREACH) && OBJECT_IS_XML(cx, obj))
? ((JSXMLObjectOps *) obj->map->ops)->
enumerateValues(cx, obj, JSENUMERATE_INIT, &state, NULL, NULL)
:
#endif
OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &state, NULL);
if (!ok)
return JS_FALSE;
iterobj->slots[JSSLOT_ITER_STATE] = state;
if (flags & JSITER_ENUMERATE) {
/*
* The enumerating iterator needs the original object to suppress
* enumeration of deleted or shadowed prototype properties. Since the
* enumerator never escapes to scripts, we use the prototype slot to
* store the original object.
*/
JS_ASSERT(obj != iterobj);
iterobj->slots[JSSLOT_PROTO] = OBJECT_TO_JSVAL(obj);
}
return JS_TRUE;
}
static JSBool
Iterator(JSContext *cx, JSObject *iterobj, uintN argc, jsval *argv, jsval *rval)
{
JSBool keyonly;
uintN flags;
JSObject *obj;
keyonly = JS_FALSE;
if (!js_ValueToBoolean(cx, argv[1], &keyonly))
return JS_FALSE;
flags = keyonly ? 0 : JSITER_FOREACH;
if (cx->fp->flags & JSFRAME_CONSTRUCTING) {
/* XXX work around old valueOf call hidden beneath js_ValueToObject */
if (!JSVAL_IS_PRIMITIVE(argv[0])) {
obj = JSVAL_TO_OBJECT(argv[0]);
} else {
obj = js_ValueToNonNullObject(cx, argv[0]);
if (!obj)
return JS_FALSE;
argv[0] = OBJECT_TO_JSVAL(obj);
}
return InitNativeIterator(cx, iterobj, obj, flags);
}
*rval = argv[0];
return js_ValueToIterator(cx, flags, rval);
}
static JSBool
NewKeyValuePair(JSContext *cx, jsid key, jsval val, jsval *rval)
{
jsval vec[2];
JSTempValueRooter tvr;
JSObject *aobj;
vec[0] = ID_TO_VALUE(key);
vec[1] = val;
JS_PUSH_TEMP_ROOT(cx, 2, vec, &tvr);
aobj = js_NewArrayObject(cx, 2, vec);
*rval = OBJECT_TO_JSVAL(aobj);
JS_POP_TEMP_ROOT(cx, &tvr);
return aobj != NULL;
}
static JSBool
IteratorNextImpl(JSContext *cx, JSObject *obj, jsval *rval)
{
JSObject *iterable;
jsval state;
uintN flags;
JSBool foreach, ok;
jsid id;
JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_IteratorClass);
iterable = OBJ_GET_PARENT(cx, obj);
JS_ASSERT(iterable);
state = OBJ_GET_SLOT(cx, obj, JSSLOT_ITER_STATE);
if (JSVAL_IS_NULL(state))
goto stop;
flags = JSVAL_TO_INT(OBJ_GET_SLOT(cx, obj, JSSLOT_ITER_FLAGS));
JS_ASSERT(!(flags & JSITER_ENUMERATE));
foreach = (flags & JSITER_FOREACH) != 0;
ok =
#if JS_HAS_XML_SUPPORT
(foreach && OBJECT_IS_XML(cx, iterable))
? ((JSXMLObjectOps *) iterable->map->ops)->
enumerateValues(cx, iterable, JSENUMERATE_NEXT, &state,
&id, rval)
:
#endif
OBJ_ENUMERATE(cx, iterable, JSENUMERATE_NEXT, &state, &id);
if (!ok)
return JS_FALSE;
OBJ_SET_SLOT(cx, obj, JSSLOT_ITER_STATE, state);
if (JSVAL_IS_NULL(state))
goto stop;
if (foreach) {
#if JS_HAS_XML_SUPPORT
if (!OBJECT_IS_XML(cx, iterable) &&
!OBJ_GET_PROPERTY(cx, iterable, id, rval)) {
return JS_FALSE;
}
#endif
if (!NewKeyValuePair(cx, id, *rval, rval))
return JS_FALSE;
} else {
*rval = ID_TO_VALUE(id);
}
return JS_TRUE;
stop:
JS_ASSERT(OBJ_GET_SLOT(cx, obj, JSSLOT_ITER_STATE) == JSVAL_NULL);
*rval = JSVAL_HOLE;
return JS_TRUE;
}
static JSBool
js_ThrowStopIteration(JSContext *cx, JSObject *obj)
{
jsval v;
JS_ASSERT(!JS_IsExceptionPending(cx));
if (js_FindClassObject(cx, NULL, INT_TO_JSID(JSProto_StopIteration), &v))
JS_SetPendingException(cx, v);
return JS_FALSE;
}
static JSBool
iterator_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
jsval *rval)
{
if (!JS_InstanceOf(cx, obj, &js_IteratorClass, argv))
return JS_FALSE;
if (!IteratorNextImpl(cx, obj, rval))
return JS_FALSE;
if (*rval == JSVAL_HOLE) {
*rval = JSVAL_NULL;
js_ThrowStopIteration(cx, obj);
return JS_FALSE;
}
return JS_TRUE;
}
static JSBool
iterator_self(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
jsval *rval)
{
*rval = OBJECT_TO_JSVAL(obj);
return JS_TRUE;
}
static JSFunctionSpec iterator_methods[] = {
{js_iterator_str, iterator_self, 0,JSPROP_READONLY|JSPROP_PERMANENT,0},
{js_next_str, iterator_next, 0,JSPROP_READONLY|JSPROP_PERMANENT,0},
{0,0,0,0,0}
};
uintN
js_GetNativeIteratorFlags(JSContext *cx, JSObject *iterobj)
{
if (OBJ_GET_CLASS(cx, iterobj) != &js_IteratorClass)
return 0;
return JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
}
void
js_CloseNativeIterator(JSContext *cx, JSObject *iterobj)
{
uintN flags;
/*
* If this iterator is not an instance of the native default iterator
* class, leave it to be GC'ed.
*/
if (!JS_InstanceOf(cx, iterobj, &js_IteratorClass, NULL))
return;
/*
* If this iterator was not created by js_ValueToIterator called from the
* for-in loop code in js_Interpret, leave it to be GC'ed.
*/
flags = JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
if (!(flags & JSITER_ENUMERATE))
return;
js_CloseIteratorState(cx, iterobj);
}
/*
* Call ToObject(v).__iterator__(keyonly) if ToObject(v).__iterator__ exists.
* Otherwise construct the defualt iterator.
*/
JSBool
js_ValueToIterator(JSContext *cx, uintN flags, jsval *vp)
{
JSObject *obj;
JSTempValueRooter tvr;
const JSAtom *atom;
JSBool ok;
JSObject *iterobj;
jsval arg;
JSString *str;
JS_ASSERT(!(flags & ~(JSITER_ENUMERATE |
JSITER_FOREACH |
JSITER_KEYVALUE)));
/* JSITER_KEYVALUE must always come with JSITER_FOREACH */
JS_ASSERT(!(flags & JSITER_KEYVALUE) || (flags & JSITER_FOREACH));
/* XXX work around old valueOf call hidden beneath js_ValueToObject */
if (!JSVAL_IS_PRIMITIVE(*vp)) {
obj = JSVAL_TO_OBJECT(*vp);
} else {
/*
* Enumerating over null and undefined gives an empty enumerator.
* This is contrary to ECMA-262 9.9 ToObject, invoked from step 3 of
* the first production in 12.6.4 and step 4 of the second production,
* but it's "web JS" compatible.
*/
if ((flags & JSITER_ENUMERATE)) {
if (!js_ValueToObject(cx, *vp, &obj))
return JS_FALSE;
if (!obj)
goto default_iter;
} else {
obj = js_ValueToNonNullObject(cx, *vp);
if (!obj)
return JS_FALSE;
}
}
JS_ASSERT(obj);
JS_PUSH_TEMP_ROOT_OBJECT(cx, obj, &tvr);
atom = cx->runtime->atomState.iteratorAtom;
#if JS_HAS_XML_SUPPORT
if (OBJECT_IS_XML(cx, obj)) {
if (!js_GetXMLFunction(cx, obj, ATOM_TO_JSID(atom), vp))
goto bad;
} else
#endif
{
if (!OBJ_GET_PROPERTY(cx, obj, ATOM_TO_JSID(atom), vp))
goto bad;
}
if (JSVAL_IS_VOID(*vp)) {
default_iter:
/*
* Fail over to the default enumerating native iterator.
*
* Create iterobj with a NULL parent to ensure that we use the correct
* scope chain to lookup the iterator's constructor. Since we use the
* parent slot to keep track of the iterable, we must fix it up after.
*/
iterobj = js_NewObject(cx, &js_IteratorClass, NULL, NULL);
if (!iterobj)
goto bad;
/* Store iterobj in *vp to protect it from GC (callers must root vp). */
*vp = OBJECT_TO_JSVAL(iterobj);
if (!InitNativeIterator(cx, iterobj, obj, flags))
goto bad;
} else {
arg = BOOLEAN_TO_JSVAL((flags & JSITER_FOREACH) == 0);
if (!js_InternalInvoke(cx, obj, *vp, JSINVOKE_ITERATOR, 1, &arg, vp))
goto bad;
if (JSVAL_IS_PRIMITIVE(*vp)) {
str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, *vp, NULL);
if (str) {
JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL,
JSMSG_BAD_ITERATOR_RETURN,
JSSTRING_CHARS(str),
JSSTRING_CHARS(ATOM_TO_STRING(atom)));
}
goto bad;
}
}
ok = JS_TRUE;
out:
if (obj)
JS_POP_TEMP_ROOT(cx, &tvr);
return ok;
bad:
ok = JS_FALSE;
goto out;
}
static JSBool
CallEnumeratorNext(JSContext *cx, JSObject *iterobj, uintN flags, jsval *rval)
{
JSObject *obj, *origobj;
jsval state;
JSBool foreach;
jsid id;
JSObject *obj2;
JSBool cond;
JSClass *clasp;
JSExtendedClass *xclasp;
JSProperty *prop;
JSString *str;
JS_ASSERT(flags & JSITER_ENUMERATE);
JS_ASSERT(JSVAL_TO_PRIVATE(iterobj->slots[JSSLOT_CLASS]) ==
&js_IteratorClass);
obj = JSVAL_TO_OBJECT(iterobj->slots[JSSLOT_PARENT]);
origobj = JSVAL_TO_OBJECT(iterobj->slots[JSSLOT_PROTO]);
state = iterobj->slots[JSSLOT_ITER_STATE];
if (JSVAL_IS_NULL(state))
goto stop;
foreach = (flags & JSITER_FOREACH) != 0;
#if JS_HAS_XML_SUPPORT
/*
* Treat an XML object specially only when it starts the prototype chain.
* Otherwise we need to do the usual deleted and shadowed property checks.
*/
if (obj == origobj && OBJECT_IS_XML(cx, obj)) {
if (foreach) {
JSXMLObjectOps *xmlops = (JSXMLObjectOps *) obj->map->ops;
if (!xmlops->enumerateValues(cx, obj, JSENUMERATE_NEXT, &state,
&id, rval)) {
return JS_FALSE;
}
} else {
if (!OBJ_ENUMERATE(cx, obj, JSENUMERATE_NEXT, &state, &id))
return JS_FALSE;
}
iterobj->slots[JSSLOT_ITER_STATE] = state;
if (JSVAL_IS_NULL(state))
goto stop;
} else
#endif
{
restart:
if (!OBJ_ENUMERATE(cx, obj, JSENUMERATE_NEXT, &state, &id))
return JS_TRUE;
iterobj->slots[JSSLOT_ITER_STATE] = state;
if (JSVAL_IS_NULL(state)) {
#if JS_HAS_XML_SUPPORT
if (OBJECT_IS_XML(cx, obj)) {
/*
* We just finished enumerating an XML obj that is present on
* the prototype chain of a non-XML origobj. Stop further
* prototype chain searches because XML objects don't
* enumerate prototypes.
*/
JS_ASSERT(origobj != obj);
JS_ASSERT(!OBJECT_IS_XML(cx, origobj));
} else
#endif
{
obj = OBJ_GET_PROTO(cx, obj);
if (obj) {
iterobj->slots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(obj);
if (!OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &state, NULL))
return JS_FALSE;
iterobj->slots[JSSLOT_ITER_STATE] = state;
if (!JSVAL_IS_NULL(state))
goto restart;
}
}
goto stop;
}
/* Skip properties not in obj when looking from origobj. */
if (!OBJ_LOOKUP_PROPERTY(cx, origobj, id, &obj2, &prop))
return JS_FALSE;
if (!prop)
goto restart;
OBJ_DROP_PROPERTY(cx, obj2, prop);
/*
* If the id was found in a prototype object or an unrelated object
* (specifically, not in an inner object for obj), skip it. This step
* means that all OBJ_LOOKUP_PROPERTY implementations must return an
* object further along on the prototype chain, or else possibly an
* object returned by the JSExtendedClass.outerObject optional hook.
*/
if (obj != obj2) {
cond = JS_FALSE;
clasp = OBJ_GET_CLASS(cx, obj2);
if (clasp->flags & JSCLASS_IS_EXTENDED) {
xclasp = (JSExtendedClass *) clasp;
cond = xclasp->outerObject &&
xclasp->outerObject(cx, obj2) == obj;
}
if (!cond)
goto restart;
}
if (foreach) {
/* Get property querying the original object. */
if (!OBJ_GET_PROPERTY(cx, origobj, id, rval))
return JS_FALSE;
}
}
if (foreach) {
if (flags & JSITER_KEYVALUE) {
if (!NewKeyValuePair(cx, id, *rval, rval))
return JS_FALSE;
}
} else {
/* Make rval a string for uniformity and compatibility. */
if (JSID_IS_ATOM(id)) {
*rval = ATOM_KEY(JSID_TO_ATOM(id));
}
#if JS_HAS_XML_SUPPORT
else if (JSID_IS_OBJECT(id)) {
str = js_ValueToString(cx, OBJECT_JSID_TO_JSVAL(id));
if (!str)
return JS_FALSE;
*rval = STRING_TO_JSVAL(str);
}
#endif
else {
str = js_NumberToString(cx, (jsdouble)JSID_TO_INT(id));
if (!str)
return JS_FALSE;
*rval = STRING_TO_JSVAL(str);
}
}
return JS_TRUE;
stop:
JS_ASSERT(iterobj->slots[JSSLOT_ITER_STATE] == JSVAL_NULL);
*rval = JSVAL_HOLE;
return JS_TRUE;
}
JSBool
js_CallIteratorNext(JSContext *cx, JSObject *iterobj, jsval *rval)
{
uintN flags;
/* Fast path for native iterators */
if (OBJ_GET_CLASS(cx, iterobj) == &js_IteratorClass) {
flags = JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
if (flags & JSITER_ENUMERATE)
return CallEnumeratorNext(cx, iterobj, flags, rval);
/*
* Call next directly as all the methods of the native iterator are
* read-only and permanent.
*/
if (!IteratorNextImpl(cx, iterobj, rval))
return JS_FALSE;
} else {
jsid id = ATOM_TO_JSID(cx->runtime->atomState.nextAtom);
if (!JS_GetMethodById(cx, iterobj, id, &iterobj, rval))
return JS_FALSE;
if (!js_InternalCall(cx, iterobj, *rval, 0, NULL, rval)) {
/* Check for StopIteration. */
if (!cx->throwing ||
JSVAL_IS_PRIMITIVE(cx->exception) ||
OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(cx->exception))
!= &js_StopIterationClass) {
return JS_FALSE;
}
/* Inline JS_ClearPendingException(cx). */
cx->throwing = JS_FALSE;
cx->exception = JSVAL_VOID;
*rval = JSVAL_HOLE;
return JS_TRUE;
}
}
return JS_TRUE;
}
static JSBool
stopiter_hasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
{
*bp = !JSVAL_IS_PRIMITIVE(v) &&
OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v)) == &js_StopIterationClass;
return JS_TRUE;
}
JSClass js_StopIterationClass = {
js_StopIteration_str,
JSCLASS_HAS_CACHED_PROTO(JSProto_StopIteration),
JS_PropertyStub, JS_PropertyStub,
JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, JS_FinalizeStub,
NULL, NULL,
NULL, NULL,
NULL, stopiter_hasInstance,
NULL, NULL
};
#if JS_HAS_GENERATORS
static void
generator_finalize(JSContext *cx, JSObject *obj)
{
JSGenerator *gen;
gen = (JSGenerator *) JS_GetPrivate(cx, obj);
if (gen) {
/*
* gen can be open on shutdown when close hooks are ignored or when
* the embedding cancels scheduled close hooks.
*/
JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_CLOSED ||
gen->state == JSGEN_OPEN);
JS_free(cx, gen);
}
}
static uint32
generator_mark(JSContext *cx, JSObject *obj, void *arg)
{
JSGenerator *gen;
gen = (JSGenerator *) JS_GetPrivate(cx, obj);
if (gen) {
/*
* We must mark argv[-2], as js_MarkStackFrame will not. Note that
* js_MarkStackFrame will mark thisp (argv[-1]) and actual arguments,
* plus any missing formals and local GC roots.
*/
JS_ASSERT(!JSVAL_IS_PRIMITIVE(gen->frame.argv[-2]));
GC_MARK(cx, JSVAL_TO_GCTHING(gen->frame.argv[-2]), "generator");
js_MarkStackFrame(cx, &gen->frame);
}
return 0;
}
JSClass js_GeneratorClass = {
js_Generator_str,
JSCLASS_HAS_PRIVATE | JSCLASS_IS_ANONYMOUS |
JSCLASS_HAS_CACHED_PROTO(JSProto_Generator),
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, generator_finalize,
NULL, NULL, NULL, NULL,
NULL, NULL, generator_mark, NULL
};
/*
* Called from the JSOP_GENERATOR case in the interpreter, with fp referring
* to the frame by which the generator function was activated. Create a new
* JSGenerator object, which contains its own JSStackFrame that we populate
* from *fp. We know that upon return, the JSOP_GENERATOR opcode will return
* from the activation in fp, so we can steal away fp->callobj and fp->argsobj
* if they are non-null.
*/
JSObject *
js_NewGenerator(JSContext *cx, JSStackFrame *fp)
{
JSObject *obj;
uintN argc, nargs, nvars, depth, nslots;
JSGenerator *gen;
jsval *newsp;
/* After the following return, failing control flow must goto bad. */
obj = js_NewObject(cx, &js_GeneratorClass, NULL, NULL);
if (!obj)
return NULL;
/* Load and compute stack slot counts. */
argc = fp->argc;
nargs = JS_MAX(argc, fp->fun->nargs);
nvars = fp->nvars;
depth = fp->script->depth;
nslots = 2 + nargs + nvars + 2 * depth;
/* Allocate obj's private data struct. */
gen = (JSGenerator *)
JS_malloc(cx, sizeof(JSGenerator) + (nslots - 1) * sizeof(jsval));
if (!gen)
goto bad;
gen->obj = obj;
/* Steal away objects reflecting fp and point them at gen->frame. */
gen->frame.callobj = fp->callobj;
if (fp->callobj) {
JS_SetPrivate(cx, fp->callobj, &gen->frame);
fp->callobj = NULL;
}
gen->frame.argsobj = fp->argsobj;
if (fp->argsobj) {
JS_SetPrivate(cx, fp->argsobj, &gen->frame);
fp->argsobj = NULL;
}
/* These two references can be shared with fp until it goes away. */
gen->frame.varobj = fp->varobj;
gen->frame.thisp = fp->thisp;
/* Copy call-invariant script and function references. */
gen->frame.script = fp->script;
gen->frame.callee = fp->callee;
gen->frame.fun = fp->fun;
/* Use newsp to carve space out of gen->stack. */
newsp = gen->stack;
gen->arena.next = NULL;
gen->arena.base = (jsuword) newsp;
gen->arena.limit = gen->arena.avail = (jsuword) (newsp + nslots);
#define COPY_STACK_ARRAY(vec,cnt,num) \
JS_BEGIN_MACRO \
gen->frame.cnt = cnt; \
gen->frame.vec = newsp; \
newsp += (num); \
memcpy(gen->frame.vec, fp->vec, (num) * sizeof(jsval)); \
JS_END_MACRO
/* Copy argv, rval, and vars. */
*newsp++ = fp->argv[-2];
*newsp++ = fp->argv[-1];
COPY_STACK_ARRAY(argv, argc, nargs);
gen->frame.rval = fp->rval;
COPY_STACK_ARRAY(vars, nvars, nvars);
#undef COPY_STACK_ARRAY
/* Initialize or copy virtual machine state. */
gen->frame.down = NULL;
gen->frame.annotation = NULL;
gen->frame.scopeChain = fp->scopeChain;
gen->frame.pc = fp->pc;
/* Allocate generating pc and operand stack space. */
gen->frame.spbase = gen->frame.sp = newsp + depth;
/* Copy remaining state (XXX sharp* and xml* should be local vars). */
gen->frame.sharpDepth = 0;
gen->frame.sharpArray = NULL;
gen->frame.flags = fp->flags | JSFRAME_GENERATOR;
gen->frame.dormantNext = NULL;
gen->frame.xmlNamespace = NULL;
gen->frame.blockChain = NULL;
/* Note that gen is newborn. */
gen->state = JSGEN_NEWBORN;
if (!JS_SetPrivate(cx, obj, gen)) {
JS_free(cx, gen);
goto bad;
}
/*
* Register with GC to ensure that suspended finally blocks will be
* executed.
*/
js_RegisterGenerator(cx, gen);
return obj;
bad:
cx->weakRoots.newborn[GCX_OBJECT] = NULL;
return NULL;
}
typedef enum JSGeneratorOp {
JSGENOP_NEXT,
JSGENOP_SEND,
JSGENOP_THROW,
JSGENOP_CLOSE
} JSGeneratorOp;
/*
* Start newborn or restart yielding generator and perform the requested
* operation inside its frame.
*/
static JSBool
SendToGenerator(JSContext *cx, JSGeneratorOp op, JSObject *obj,
JSGenerator *gen, jsval arg, jsval *rval)
{
JSStackFrame *fp;
jsval junk;
JSArena *arena;
JSBool ok;
JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_OPEN);
switch (op) {
case JSGENOP_NEXT:
case JSGENOP_SEND:
if (gen->state == JSGEN_OPEN) {
/*
* Store the argument to send as the result of the yield
* expression.
*/
gen->frame.sp[-1] = arg;
}
gen->state = JSGEN_RUNNING;
break;
case JSGENOP_THROW:
JS_SetPendingException(cx, arg);
gen->state = JSGEN_RUNNING;
break;
default:
JS_ASSERT(op == JSGENOP_CLOSE);
JS_SetPendingException(cx, JSVAL_ARETURN);
gen->state = JSGEN_CLOSING;
break;
}
/* Extend the current stack pool with gen->arena. */
arena = cx->stackPool.current;
JS_ASSERT(!arena->next);
JS_ASSERT(!gen->arena.next);
JS_ASSERT(cx->stackPool.current != &gen->arena);
cx->stackPool.current = arena->next = &gen->arena;
/* Push gen->frame around the interpreter activation. */
fp = cx->fp;
cx->fp = &gen->frame;
gen->frame.down = fp;
ok = js_Interpret(cx, gen->frame.pc, &junk);
cx->fp = fp;
gen->frame.down = NULL;
/* Retract the stack pool and sanitize gen->arena. */
JS_ASSERT(!gen->arena.next);
JS_ASSERT(arena->next == &gen->arena);
JS_ASSERT(cx->stackPool.current == &gen->arena);
cx->stackPool.current = arena;
arena->next = NULL;
if (gen->frame.flags & JSFRAME_YIELDING) {
/* Yield cannot fail, throw or be called on closing. */
JS_ASSERT(ok);
JS_ASSERT(!cx->throwing);
JS_ASSERT(gen->state == JSGEN_RUNNING);
JS_ASSERT(op != JSGENOP_CLOSE);
gen->frame.flags &= ~JSFRAME_YIELDING;
gen->state = JSGEN_OPEN;
*rval = gen->frame.rval;
return JS_TRUE;
}
gen->state = JSGEN_CLOSED;
if (ok) {
/* Returned, explicitly or by falling off the end. */
if (op == JSGENOP_CLOSE)
return JS_TRUE;
return js_ThrowStopIteration(cx, obj);
}
/*
* An error, silent termination by branch callback or an exception.
* Propagate the condition to the caller.
*/
return JS_FALSE;
}
/*
* Execute gen's close hook after the GC detects that the object has become
* unreachable.
*/
JSBool
js_CloseGeneratorObject(JSContext *cx, JSGenerator *gen)
{
/* We pass null as rval since SendToGenerator never uses it with CLOSE. */
return SendToGenerator(cx, JSGENOP_CLOSE, gen->obj, gen, JSVAL_VOID, NULL);
}
/*
* Common subroutine of generator_(next|send|throw|close) methods.
*/
static JSBool
generator_op(JSContext *cx, JSGeneratorOp op,
JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
JSGenerator *gen;
JSString *str;
jsval arg;
if (!JS_InstanceOf(cx, obj, &js_GeneratorClass, argv))
return JS_FALSE;
gen = (JSGenerator *) JS_GetPrivate(cx, obj);
if (gen == NULL) {
/* This happens when obj is the generator prototype. See bug 352885. */
goto closed_generator;
}
switch (gen->state) {
case JSGEN_NEWBORN:
switch (op) {
case JSGENOP_NEXT:
case JSGENOP_THROW:
break;
case JSGENOP_SEND:
if (!JSVAL_IS_VOID(argv[0])) {
str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK,
argv[0], NULL);
if (str) {
JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL,
JSMSG_BAD_GENERATOR_SEND,
JSSTRING_CHARS(str));
}
return JS_FALSE;
}
break;
default:
JS_ASSERT(op == JSGENOP_CLOSE);
gen->state = JSGEN_CLOSED;
return JS_TRUE;
}
break;
case JSGEN_OPEN:
break;
case JSGEN_RUNNING:
case JSGEN_CLOSING:
str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, argv[-1],
JS_GetFunctionId(gen->frame.fun));
if (str) {
JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL,
JSMSG_NESTING_GENERATOR,
JSSTRING_CHARS(str));
}
return JS_FALSE;
default:
JS_ASSERT(gen->state == JSGEN_CLOSED);
closed_generator:
switch (op) {
case JSGENOP_NEXT:
case JSGENOP_SEND:
return js_ThrowStopIteration(cx, obj);
case JSGENOP_THROW:
JS_SetPendingException(cx, argv[0]);
return JS_FALSE;
default:
JS_ASSERT(op == JSGENOP_CLOSE);
return JS_TRUE;
}
}
arg = (op == JSGENOP_SEND || op == JSGENOP_THROW)
? argv[0]
: JSVAL_VOID;
if (!SendToGenerator(cx, op, obj, gen, arg, rval))
return JS_FALSE;
return JS_TRUE;
}
static JSBool
generator_send(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
jsval *rval)
{
return generator_op(cx, JSGENOP_SEND, obj, argc, argv, rval);
}
static JSBool
generator_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
jsval *rval)
{
return generator_op(cx, JSGENOP_NEXT, obj, argc, argv, rval);
}
static JSBool
generator_throw(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
jsval *rval)
{
return generator_op(cx, JSGENOP_THROW, obj, argc, argv, rval);
}
static JSBool
generator_close(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
jsval *rval)
{
return generator_op(cx, JSGENOP_CLOSE, obj, argc, argv, rval);
}
static JSFunctionSpec generator_methods[] = {
{js_iterator_str, iterator_self, 0,JSPROP_READONLY|JSPROP_PERMANENT,0},
{js_next_str, generator_next, 0,JSPROP_READONLY|JSPROP_PERMANENT,0},
{js_send_str, generator_send, 1,JSPROP_READONLY|JSPROP_PERMANENT,0},
{js_throw_str, generator_throw, 1,JSPROP_READONLY|JSPROP_PERMANENT,0},
{js_close_str, generator_close, 0,JSPROP_READONLY|JSPROP_PERMANENT,0},
{0,0,0,0,0}
};
#endif /* JS_HAS_GENERATORS */
JSObject *
js_InitIteratorClasses(JSContext *cx, JSObject *obj)
{
JSObject *proto, *stop;
/* Idempotency required: we initialize several things, possibly lazily. */
if (!js_GetClassObject(cx, obj, JSProto_StopIteration, &stop))
return NULL;
if (stop)
return stop;
proto = JS_InitClass(cx, obj, NULL, &js_IteratorClass, Iterator, 2,
NULL, iterator_methods, NULL, NULL);
if (!proto)
return NULL;
proto->slots[JSSLOT_ITER_STATE] = JSVAL_NULL;
#if JS_HAS_GENERATORS
/* Initialize the generator internals if configured. */
if (!JS_InitClass(cx, obj, NULL, &js_GeneratorClass, NULL, 0,
NULL, generator_methods, NULL, NULL)) {
return NULL;
}
#endif
return JS_InitClass(cx, obj, NULL, &js_StopIterationClass, NULL, 0,
NULL, NULL, NULL, NULL);
}