/* -*- 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 /* 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 "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 }; #if JS_HAS_GENERATORS static JSBool Iterator(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { jsval fval; const jsid id = ATOM_TO_JSID(cx->runtime->atomState.iteratorAtom); /* 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; } return JS_GetMethodById(cx, obj, id, &obj, &fval) && js_InternalCall(cx, obj, fval, argc - 1, argv + 1, rval); } static JSBool NewKeyValuePair(JSContext *cx, jsid key, jsval val, jsval *rval) { jsval vec[2]; JSObject *aobj; vec[0] = ID_TO_VALUE(key); vec[1] = val; aobj = js_NewArrayObject(cx, 2, vec); if (!aobj) return JS_FALSE; *rval = OBJECT_TO_JSVAL(aobj); return JS_TRUE; } static JSBool SetupKeyValueReturn(JSContext *cx, jsid id, jsval val, jsval *rval) { #if JS_HAS_LVALUE_RETURN cx->rval2 = (jsval) id; cx->rval2set = JS_RVAL2_ITERKEY; *rval = val; return JS_TRUE; #else return NewKeyValuePair(cx, id, val, rval); #endif } static JSBool CheckKeyValueReturn(JSContext *cx, uintN flags, jsid *idp, jsval *rval) { jsval val, idval; JSBool arraylike; jsuint length; JSObject *obj; val = *rval; #if JS_HAS_LVALUE_RETURN if (cx->rval2set == JS_RVAL2_ITERKEY) { cx->rval2set = JS_RVAL2_CLEAR; if (idp) *idp = (jsid) cx->rval2; if (!idp || (flags & JSITER_KEYVALUE)) return NewKeyValuePair(cx, (jsid) cx->rval2, val, rval); return JS_TRUE; } #endif if (!idp) return JS_TRUE; arraylike = JS_FALSE; length = 0; /* quell GCC overwarnings */ obj = NULL; if (!JSVAL_IS_PRIMITIVE(val)) { obj = JSVAL_TO_OBJECT(val); if (!js_IsArrayLike(cx, obj, &arraylike, &length)) return JS_FALSE; } if (arraylike && length == 2) { if (!OBJ_GET_PROPERTY(cx, obj, INT_TO_JSID(0), &idval)) return JS_FALSE; if (!JS_ValueToId(cx, idval, idp)) return JS_FALSE; if (flags & JSITER_KEYVALUE) return JS_TRUE; return OBJ_GET_PROPERTY(cx, obj, INT_TO_JSID(1), rval); } return JS_ValueToId(cx, val, idp); } static JSBool iterator_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSObject *iterable; jsval state, val; uintN flags; JSBool foreach, ok; jsid id; if (!JS_InstanceOf(cx, obj, &js_IteratorClass, argv)) return JS_FALSE; iterable = OBJ_GET_PARENT(cx, obj); 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)); 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, &val) : #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, &val)) { return JS_FALSE; } #endif if (!SetupKeyValueReturn(cx, id, val, rval)) return JS_FALSE; } else { *rval = ID_TO_VALUE(id); } return JS_TRUE; stop: js_ThrowStopIteration(cx, obj); return JS_FALSE; } 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,0,0}, {js_next_str, iterator_next, 0,0,0}, {0,0,0,0,0} }; #endif /* JS_HAS_GENERATORS */ JSBool js_NewNativeIterator(JSContext *cx, JSObject *obj, uintN flags, jsval *vp) { JSObject *iterobj; jsval state; JSBool ok; /* * 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 later. */ iterobj = js_NewObject(cx, &js_IteratorClass, NULL, NULL); if (!iterobj) return JS_FALSE; /* Store iterobj in *vp to protect it from GC (callers must root vp). */ *vp = OBJECT_TO_JSVAL(iterobj); /* 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; 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; return JS_TRUE; } 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 (!JS_InstanceOf(cx, iterobj, &js_IteratorClass, NULL)) return; /* * We are called only for new iterators. Old iterators must be GCed. * Even in the new case, iterobj could have escaped, so we must test * JSITER_HIDDEN. */ flags = JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS)); JS_ASSERT(!(flags & JSITER_COMPAT)); if (!(flags & JSITER_HIDDEN)) return; /* * Clear the cached iterator object member of cx. Normally the GC clears * all contexts' cachedIterObj members, but JSOP_ENDITER calls us eagerly * to close iterobj. */ if (iterobj == cx->cachedIterObj) cx->cachedIterObj = NULL; js_CloseIteratorState(cx, iterobj); } JSBool js_DefaultIterator(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSBool keyonly; if (OBJ_GET_CLASS(cx, obj) == &js_IteratorClass) { *rval = OBJECT_TO_JSVAL(obj); return JS_TRUE; } keyonly = JS_FALSE; if (argc != 0 && !js_ValueToBoolean(cx, argv[0], &keyonly)) return JS_FALSE; return js_NewNativeIterator(cx, obj, keyonly ? 0 : JSITER_FOREACH, rval); } /* * Inline expansion of Iterator, with extra logic to constrain the result of * ToObject(v).__iterator__. */ JSObject * js_ValueToIterator(JSContext *cx, jsval v, uintN flags) { JSObject *obj, *iterobj; JSTempValueRooter tvr; jsval arg, fval, rval; JSString *str; JSFunction *fun; const JSAtom *atom = cx->runtime->atomState.iteratorAtom; /* XXX work around old valueOf call hidden beneath js_ValueToObject */ if (!JSVAL_IS_PRIMITIVE(v)) { obj = JSVAL_TO_OBJECT(v); } else { obj = js_ValueToNonNullObject(cx, v); if (!obj) return NULL; } arg = BOOLEAN_TO_JSVAL((flags & JSITER_FOREACH) == 0); JS_PUSH_SINGLE_TEMP_ROOT(cx, obj, &tvr); if (!JS_GetMethodById(cx, obj, ATOM_TO_JSID(atom), &obj, &fval)) goto bad; if (JSVAL_IS_VOID(fval)) { /* Fail over to the default native iterator, called directly. */ if (!js_DefaultIterator(cx, obj, 1, &arg, &rval)) goto bad; if (JSVAL_IS_PRIMITIVE(rval)) goto bad_iterator; iterobj = JSVAL_TO_OBJECT(rval); JS_ASSERT(OBJ_GET_CLASS(cx, iterobj) == &js_IteratorClass); iterobj->slots[JSSLOT_ITER_FLAGS] |= INT_TO_JSVAL(JSITER_HIDDEN); goto out; } if (!js_InternalInvoke(cx, obj, fval, JSINVOKE_ITERATOR, 1, &arg, &rval)) goto bad; if (JSVAL_IS_PRIMITIVE(rval)) goto bad_iterator; iterobj = JSVAL_TO_OBJECT(rval); /* * If __iterator__ is the default native method, the native iterator it * returns can be flagged as hidden from script access. This flagging is * predicated on js_ValueToIterator being called only by the for-in loop * code -- the js_CloseNativeIteration early-finalization optimization * based on it will break badly if script can reach iterobj. */ if (OBJ_GET_CLASS(cx, iterobj) == &js_IteratorClass && VALUE_IS_FUNCTION(cx, fval)) { fun = (JSFunction *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(fval)); if (!FUN_INTERPRETED(fun) && fun->u.n.native == js_DefaultIterator) iterobj->slots[JSSLOT_ITER_FLAGS] |= INT_TO_JSVAL(JSITER_HIDDEN); } out: JS_POP_TEMP_ROOT(cx, &tvr); return iterobj; bad: iterobj = NULL; goto out; bad_iterator: str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NULL); if (str) { JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL, JSMSG_BAD_ITERATOR_RETURN, JSSTRING_CHARS(str), JSSTRING_CHARS(ATOM_TO_STRING(atom))); } goto bad; } JSBool js_CallIteratorNext(JSContext *cx, JSObject *iterobj, uintN flags, jsid *idp, jsval *rval) { JSBool unlock; JSObject *obj; JSScope *scope; JSScopeProperty *sprop; jsval fval; JSFunction *fun; const jsid id = ATOM_TO_JSID(cx->runtime->atomState.nextAtom); /* Fastest path for repeated call from for-in loop bytecode. */ if (iterobj == cx->cachedIterObj) { JS_ASSERT(OBJ_GET_CLASS(cx, iterobj) == &js_IteratorClass); JS_ASSERT(flags & JSITER_HIDDEN); if (!iterator_next(cx, iterobj, 0, NULL, rval) || !CheckKeyValueReturn(cx, flags, idp, rval)) { cx->cachedIterObj = NULL; return JS_FALSE; } return JS_TRUE; } /* Fast path for native iterator with unoverridden .next() method. */ unlock = JS_TRUE; obj = iterobj; JS_LOCK_OBJ(cx, obj); scope = OBJ_SCOPE(obj); sprop = NULL; while (LOCKED_OBJ_GET_CLASS(obj) == &js_IteratorClass) { obj = scope->object; sprop = SCOPE_GET_PROPERTY(scope, id); if (sprop) break; obj = LOCKED_OBJ_GET_PROTO(obj); if (!obj) break; JS_UNLOCK_SCOPE(cx, scope); scope = OBJ_SCOPE(obj); JS_LOCK_SCOPE(cx, scope); } if (sprop && SPROP_HAS_VALID_SLOT(sprop, scope)) { /* * Unlock scope as soon as we fetch fval, and clear the unlock flag in * case we do not return early after setting cx->cachedIterObj. */ fval = LOCKED_OBJ_GET_SLOT(obj, sprop->slot); JS_UNLOCK_SCOPE(cx, scope); unlock = JS_FALSE; if (VALUE_IS_FUNCTION(cx, fval)) { fun = (JSFunction *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(fval)); if (!FUN_INTERPRETED(fun) && fun->u.n.native == iterator_next) { if (!iterator_next(cx, iterobj, 0, NULL, rval) || !CheckKeyValueReturn(cx, flags, idp, rval)) { return JS_FALSE; } if (flags & JSITER_HIDDEN) cx->cachedIterObj = iterobj; return JS_TRUE; } } } if (unlock) JS_UNLOCK_SCOPE(cx, scope); return JS_GetMethodById(cx, iterobj, id, &iterobj, &fval) && js_InternalCall(cx, iterobj, fval, 0, NULL, rval) && CheckKeyValueReturn(cx, flags, idp, rval); } static JSBool exception_getName(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { JSClass *clasp; JSProtoKey key; clasp = OBJ_GET_CLASS(cx, obj); key = JSCLASS_CACHED_PROTO_KEY(clasp); *vp = (key != JSProto_Null) ? ATOM_KEY(cx->runtime->atomState.classAtoms[key]) : STRING_TO_JSVAL(cx->runtime->emptyString); return JS_TRUE; } static JSPropertySpec exception_props[] = { {js_name_str, 0, JSPROP_READONLY|JSPROP_PERMANENT, exception_getName, NULL}, {0,0,0,0,0} }; 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 }; static JSBool genexit_hasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp) { *bp = !JSVAL_IS_PRIMITIVE(v) && OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v)) == &js_GeneratorExitClass; return JS_TRUE; } JSClass js_GeneratorExitClass = { js_GeneratorExit_str, JSCLASS_HAS_CACHED_PROTO(JSProto_GeneratorExit), JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, NULL, NULL, NULL, NULL, NULL, genexit_hasInstance, NULL, NULL }; 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; } #if JS_HAS_GENERATORS typedef enum JSGeneratorState { JSGEN_NEWBORN, JSGEN_RUNNING, JSGEN_CLOSED } JSGeneratorState; typedef struct JSGenerator { JSGeneratorState state; JSStackFrame frame; JSArena arena; jsval stack[1]; } JSGenerator; static void generator_closehook(JSContext *cx, JSObject *obj) { JSGenerator *gen; jsval fval, rval; const jsid id = ATOM_TO_JSID(cx->runtime->atomState.closeAtom); gen = (JSGenerator *) JS_GetPrivate(cx, obj); if (!gen) return; /* * Ignore errors until after we call the close method, then force prompt * error reporting, since GC is infallible. */ if (JS_GetMethodById(cx, obj, id, &obj, &fval)) js_InternalCall(cx, obj, fval, 0, NULL, &rval); if (cx->throwing && !js_ReportUncaughtException(cx)) JS_ClearPendingException(cx); } static void generator_finalize(JSContext *cx, JSObject *obj) { JSGenerator *gen; gen = (JSGenerator *) JS_GetPrivate(cx, obj); if (gen) JS_free(cx, gen); } static uint32 generator_mark(JSContext *cx, JSObject *obj, void *arg) { JSGenerator *gen; gen = (JSGenerator *) JS_GetPrivate(cx, obj); if (gen && gen->state != JSGEN_CLOSED) { /* * 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; } JSExtendedClass js_GeneratorClass = { { js_Generator_str, JSCLASS_HAS_PRIVATE | JSCLASS_IS_ANONYMOUS | JSCLASS_IS_EXTENDED | 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 }, NULL, NULL, NULL, generator_closehook, JSCLASS_NO_RESERVED_MEMBERS }; JSObject * js_NewGenerator(JSContext *cx, JSStackFrame *fp) { JSObject *obj; uintN argc, nargs, nvars, depth, nslots; JSGenerator *gen; jsval *newsp; obj = js_NewObject(cx, &js_GeneratorClass.base, 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 || !JS_SetPrivate(cx, obj, gen)) { JS_free(cx, gen); return NULL; } /* Copy call-invariant object and function references. */ gen->frame.callobj = fp->callobj; gen->frame.argsobj = fp->argsobj; gen->frame.varobj = fp->varobj; gen->frame.script = fp->script; gen->frame.fun = fp->fun; gen->frame.thisp = fp->thisp; gen->arena.next = NULL; /* Use newsp to carve space out of gen->stack. */ newsp = gen->stack; 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; gen->frame.dormantNext = NULL; gen->frame.xmlNamespace = NULL; gen->frame.blockChain = NULL; /* Note that gen is newborn. */ gen->state = JSGEN_NEWBORN; return obj; } static JSBool generator_send(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSGenerator *gen; JSString *str; JSStackFrame *fp; JSArena *arena; JSBool ok; jsval junk; if (!JS_InstanceOf(cx, obj, &js_GeneratorClass.base, argv)) return JS_FALSE; gen = (JSGenerator *)JS_GetPrivate(cx, obj); if (!gen || gen->state == JSGEN_CLOSED) return !JS_IsExceptionPending(cx) && js_ThrowStopIteration(cx, obj); if (gen->state == JSGEN_NEWBORN && argc != 0 && !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; } fp = cx->fp; arena = cx->stackPool.current; cx->stackPool.current = &gen->arena; cx->fp = &gen->frame; gen->frame.down = fp; /* Store the argument to send as the result of the yield expression. */ gen->frame.sp[-1] = (argc != 0) ? argv[0] : JSVAL_VOID; ok = js_Interpret(cx, gen->frame.pc, &junk); cx->fp = fp; cx->stackPool.current = arena; if (!ok) { if (cx->throwing) gen->state = JSGEN_CLOSED; return JS_FALSE; } if (!(gen->frame.flags & JSFRAME_YIELDING)) { /* Returned, explicitly or by falling off the end. */ gen->state = JSGEN_CLOSED; return js_ThrowStopIteration(cx, obj); } gen->state = JSGEN_RUNNING; gen->frame.flags &= ~JSFRAME_YIELDING; *rval = gen->frame.rval; return JS_TRUE; } static JSBool generator_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { return generator_send(cx, obj, 0, argv, rval); } static JSBool generator_throw(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JS_SetPendingException(cx, argv[0]); return generator_send(cx, obj, 0, argv, rval); } static JSBool generator_close(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { jsval genexit, exn; JSClass *clasp; JSString *str; if (!js_FindClassObject(cx, NULL, INT_TO_JSID(JSProto_GeneratorExit), &genexit)) { return JS_FALSE; } JS_SetPendingException(cx, genexit); if (generator_send(cx, obj, 0, argv, rval)) return JS_TRUE; if (cx->throwing) { exn = cx->exception; if (!JSVAL_IS_PRIMITIVE(exn)) { clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(exn)); if (clasp == &js_GeneratorExitClass || clasp == &js_StopIterationClass) { JS_ClearPendingException(cx); return JS_TRUE; } } } str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, argv[-1], NULL); if (str) { JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL, JSMSG_BAD_GENERATOR_EXIT, JSSTRING_CHARS(str)); } return JS_FALSE; } static JSFunctionSpec generator_methods[] = { {js_iterator_str, iterator_self, 0,0,0}, {js_next_str, generator_next, 0,0,0}, {js_send_str, generator_send, 1,0,0}, {js_throw_str, generator_throw, 1,0,0}, {js_close_str, generator_close, 0,0,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; #if JS_HAS_GENERATORS /* Expose Iterator and initialize the generator internals if configured. */ 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_InitClass(cx, obj, NULL, &js_GeneratorClass.base, NULL, 0, NULL, generator_methods, NULL, NULL)) { return NULL; } #endif /* * Always initialize StopIteration, it's used by for-in loop interpreter * code even if iterators and generators are deconfigured. */ if (!js_GetClassPrototype(cx, NULL, INT_TO_JSID(JSProto_Error), &proto)) return NULL; #if JS_HAS_GENERATORS if (!JS_InitClass(cx, obj, proto, &js_GeneratorExitClass, NULL, 0, exception_props, NULL, NULL, NULL)) { return NULL; } #endif return JS_InitClass(cx, obj, proto, &js_StopIterationClass, NULL, 0, exception_props, NULL, NULL, NULL); }