Save all modification
[mozilla-1.9/m8.git] / js / src / jsiter.c
blob43dbfe6d1d00897ca093d1db3fed77a4efae3874
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sw=4 et tw=78:
4 * ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
17 * The Original Code is Mozilla Communicator client code, released
18 * March 31, 1998.
20 * The Initial Developer of the Original Code is
21 * Netscape Communications Corporation.
22 * Portions created by the Initial Developer are Copyright (C) 1998
23 * the Initial Developer. All Rights Reserved.
25 * Contributor(s):
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
42 * JavaScript iterators.
44 #include "jsstddef.h"
45 #include <string.h> /* for memcpy */
46 #include "jstypes.h"
47 #include "jsutil.h"
48 #include "jsarena.h"
49 #include "jsapi.h"
50 #include "jsarray.h"
51 #include "jsatom.h"
52 #include "jsbool.h"
53 #include "jscntxt.h"
54 #include "jsconfig.h"
55 #include "jsexn.h"
56 #include "jsfun.h"
57 #include "jsgc.h"
58 #include "jsinterp.h"
59 #include "jsiter.h"
60 #include "jslock.h"
61 #include "jsnum.h"
62 #include "jsobj.h"
63 #include "jsopcode.h"
64 #include "jsscan.h"
65 #include "jsscope.h"
66 #include "jsscript.h"
68 #if JS_HAS_XML_SUPPORT
69 #include "jsxml.h"
70 #endif
72 #define JSSLOT_ITER_STATE (JSSLOT_PRIVATE)
73 #define JSSLOT_ITER_FLAGS (JSSLOT_PRIVATE + 1)
75 #if JSSLOT_ITER_FLAGS >= JS_INITIAL_NSLOTS
76 #error JS_INITIAL_NSLOTS must be greater than JSSLOT_ITER_FLAGS.
77 #endif
79 #if JS_HAS_GENERATORS
81 static JSBool
82 CloseGenerator(JSContext *cx, JSObject *genobj);
84 #endif
87 * Shared code to close iterator's state either through an explicit call or
88 * when GC detects that the iterator is no longer reachable.
90 void
91 js_CloseNativeIterator(JSContext *cx, JSObject *iterobj)
93 jsval state;
94 JSObject *iterable;
96 JS_ASSERT(STOBJ_GET_CLASS(iterobj) == &js_IteratorClass);
98 /* Avoid double work if js_CloseNativeIterator was called on obj. */
99 state = STOBJ_GET_SLOT(iterobj, JSSLOT_ITER_STATE);
100 if (JSVAL_IS_NULL(state))
101 return;
103 /* Protect against failure to fully initialize obj. */
104 iterable = STOBJ_GET_PARENT(iterobj);
105 if (iterable) {
106 #if JS_HAS_XML_SUPPORT
107 uintN flags = JSVAL_TO_INT(STOBJ_GET_SLOT(iterobj, JSSLOT_ITER_FLAGS));
108 if ((flags & JSITER_FOREACH) && OBJECT_IS_XML(cx, iterable)) {
109 ((JSXMLObjectOps *) iterable->map->ops)->
110 enumerateValues(cx, iterable, JSENUMERATE_DESTROY, &state,
111 NULL, NULL);
112 } else
113 #endif
114 OBJ_ENUMERATE(cx, iterable, JSENUMERATE_DESTROY, &state, NULL);
116 STOBJ_SET_SLOT(iterobj, JSSLOT_ITER_STATE, JSVAL_NULL);
119 JSClass js_IteratorClass = {
120 "Iterator",
121 JSCLASS_HAS_RESERVED_SLOTS(2) | /* slots for state and flags */
122 JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator),
123 JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
124 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
125 JSCLASS_NO_OPTIONAL_MEMBERS
128 static JSBool
129 InitNativeIterator(JSContext *cx, JSObject *iterobj, JSObject *obj, uintN flags)
131 jsval state;
132 JSBool ok;
134 JS_ASSERT(STOBJ_GET_CLASS(iterobj) == &js_IteratorClass);
136 /* Initialize iterobj in case of enumerate hook failure. */
137 STOBJ_SET_PARENT(iterobj, obj);
138 STOBJ_SET_SLOT(iterobj, JSSLOT_ITER_STATE, JSVAL_NULL);
139 STOBJ_SET_SLOT(iterobj, JSSLOT_ITER_FLAGS, INT_TO_JSVAL(flags));
140 if (!js_RegisterCloseableIterator(cx, iterobj))
141 return JS_FALSE;
142 if (!obj)
143 return JS_TRUE;
145 ok =
146 #if JS_HAS_XML_SUPPORT
147 ((flags & JSITER_FOREACH) && OBJECT_IS_XML(cx, obj))
148 ? ((JSXMLObjectOps *) obj->map->ops)->
149 enumerateValues(cx, obj, JSENUMERATE_INIT, &state, NULL, NULL)
151 #endif
152 OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &state, NULL);
153 if (!ok)
154 return JS_FALSE;
156 STOBJ_SET_SLOT(iterobj, JSSLOT_ITER_STATE, state);
157 if (flags & JSITER_ENUMERATE) {
159 * The enumerating iterator needs the original object to suppress
160 * enumeration of deleted or shadowed prototype properties. Since the
161 * enumerator never escapes to scripts, we use the prototype slot to
162 * store the original object.
164 JS_ASSERT(obj != iterobj);
165 STOBJ_SET_PROTO(iterobj, obj);
167 return JS_TRUE;
170 static JSBool
171 Iterator(JSContext *cx, JSObject *iterobj, uintN argc, jsval *argv, jsval *rval)
173 JSBool keyonly;
174 uintN flags;
175 JSObject *obj;
177 keyonly = JS_FALSE;
178 if (!js_ValueToBoolean(cx, argv[1], &keyonly))
179 return JS_FALSE;
180 flags = keyonly ? 0 : JSITER_FOREACH;
182 if (cx->fp->flags & JSFRAME_CONSTRUCTING) {
183 /* XXX work around old valueOf call hidden beneath js_ValueToObject */
184 if (!JSVAL_IS_PRIMITIVE(argv[0])) {
185 obj = JSVAL_TO_OBJECT(argv[0]);
186 } else {
187 obj = js_ValueToNonNullObject(cx, argv[0]);
188 if (!obj)
189 return JS_FALSE;
190 argv[0] = OBJECT_TO_JSVAL(obj);
192 return InitNativeIterator(cx, iterobj, obj, flags);
195 *rval = argv[0];
196 return js_ValueToIterator(cx, flags, rval);
199 static JSBool
200 NewKeyValuePair(JSContext *cx, jsid key, jsval val, jsval *rval)
202 jsval vec[2];
203 JSTempValueRooter tvr;
204 JSObject *aobj;
206 vec[0] = ID_TO_VALUE(key);
207 vec[1] = val;
209 JS_PUSH_TEMP_ROOT(cx, 2, vec, &tvr);
210 aobj = js_NewArrayObject(cx, 2, vec);
211 *rval = OBJECT_TO_JSVAL(aobj);
212 JS_POP_TEMP_ROOT(cx, &tvr);
214 return aobj != NULL;
217 static JSBool
218 IteratorNextImpl(JSContext *cx, JSObject *obj, jsval *rval)
220 JSObject *iterable;
221 jsval state;
222 uintN flags;
223 JSBool foreach, ok;
224 jsid id;
226 JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_IteratorClass);
228 iterable = OBJ_GET_PARENT(cx, obj);
229 JS_ASSERT(iterable);
230 state = OBJ_GET_SLOT(cx, obj, JSSLOT_ITER_STATE);
231 if (JSVAL_IS_NULL(state))
232 goto stop;
234 flags = JSVAL_TO_INT(OBJ_GET_SLOT(cx, obj, JSSLOT_ITER_FLAGS));
235 JS_ASSERT(!(flags & JSITER_ENUMERATE));
236 foreach = (flags & JSITER_FOREACH) != 0;
237 ok =
238 #if JS_HAS_XML_SUPPORT
239 (foreach && OBJECT_IS_XML(cx, iterable))
240 ? ((JSXMLObjectOps *) iterable->map->ops)->
241 enumerateValues(cx, iterable, JSENUMERATE_NEXT, &state,
242 &id, rval)
244 #endif
245 OBJ_ENUMERATE(cx, iterable, JSENUMERATE_NEXT, &state, &id);
246 if (!ok)
247 return JS_FALSE;
249 OBJ_SET_SLOT(cx, obj, JSSLOT_ITER_STATE, state);
250 if (JSVAL_IS_NULL(state))
251 goto stop;
253 if (foreach) {
254 #if JS_HAS_XML_SUPPORT
255 if (!OBJECT_IS_XML(cx, iterable) &&
256 !OBJ_GET_PROPERTY(cx, iterable, id, rval)) {
257 return JS_FALSE;
259 #endif
260 if (!NewKeyValuePair(cx, id, *rval, rval))
261 return JS_FALSE;
262 } else {
263 *rval = ID_TO_VALUE(id);
265 return JS_TRUE;
267 stop:
268 JS_ASSERT(OBJ_GET_SLOT(cx, obj, JSSLOT_ITER_STATE) == JSVAL_NULL);
269 *rval = JSVAL_HOLE;
270 return JS_TRUE;
273 static JSBool
274 js_ThrowStopIteration(JSContext *cx, JSObject *obj)
276 jsval v;
278 JS_ASSERT(!JS_IsExceptionPending(cx));
279 if (js_FindClassObject(cx, NULL, INT_TO_JSID(JSProto_StopIteration), &v))
280 JS_SetPendingException(cx, v);
281 return JS_FALSE;
284 static JSBool
285 iterator_next(JSContext *cx, uintN argc, jsval *vp)
287 JSObject *obj;
289 obj = JSVAL_TO_OBJECT(vp[1]);
290 if (!JS_InstanceOf(cx, obj, &js_IteratorClass, vp + 2))
291 return JS_FALSE;
293 if (!IteratorNextImpl(cx, obj, vp))
294 return JS_FALSE;
296 if (*vp == JSVAL_HOLE) {
297 *vp = JSVAL_NULL;
298 js_ThrowStopIteration(cx, obj);
299 return JS_FALSE;
301 return JS_TRUE;
304 static JSBool
305 iterator_self(JSContext *cx, uintN argc, jsval *vp)
307 *vp = vp[1];
308 return JS_TRUE;
311 #define JSPROP_ROPERM (JSPROP_READONLY | JSPROP_PERMANENT)
313 static JSFunctionSpec iterator_methods[] = {
314 JS_FN(js_iterator_str, iterator_self, 0,0,JSPROP_ROPERM,0),
315 JS_FN(js_next_str, iterator_next, 0,0,JSPROP_ROPERM,0),
316 JS_FS_END
319 uintN
320 js_GetNativeIteratorFlags(JSContext *cx, JSObject *iterobj)
322 if (OBJ_GET_CLASS(cx, iterobj) != &js_IteratorClass)
323 return 0;
324 return JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
328 * Call ToObject(v).__iterator__(keyonly) if ToObject(v).__iterator__ exists.
329 * Otherwise construct the defualt iterator.
331 JSBool
332 js_ValueToIterator(JSContext *cx, uintN flags, jsval *vp)
334 JSObject *obj;
335 JSTempValueRooter tvr;
336 JSAtom *atom;
337 JSBool ok;
338 JSObject *iterobj;
339 jsval arg;
341 JS_ASSERT(!(flags & ~(JSITER_ENUMERATE |
342 JSITER_FOREACH |
343 JSITER_KEYVALUE)));
345 /* JSITER_KEYVALUE must always come with JSITER_FOREACH */
346 JS_ASSERT(!(flags & JSITER_KEYVALUE) || (flags & JSITER_FOREACH));
348 /* XXX work around old valueOf call hidden beneath js_ValueToObject */
349 if (!JSVAL_IS_PRIMITIVE(*vp)) {
350 obj = JSVAL_TO_OBJECT(*vp);
351 } else {
353 * Enumerating over null and undefined gives an empty enumerator.
354 * This is contrary to ECMA-262 9.9 ToObject, invoked from step 3 of
355 * the first production in 12.6.4 and step 4 of the second production,
356 * but it's "web JS" compatible.
358 if ((flags & JSITER_ENUMERATE)) {
359 if (!js_ValueToObject(cx, *vp, &obj))
360 return JS_FALSE;
361 if (!obj)
362 goto default_iter;
363 } else {
364 obj = js_ValueToNonNullObject(cx, *vp);
365 if (!obj)
366 return JS_FALSE;
370 JS_ASSERT(obj);
371 JS_PUSH_TEMP_ROOT_OBJECT(cx, obj, &tvr);
373 atom = cx->runtime->atomState.iteratorAtom;
374 #if JS_HAS_XML_SUPPORT
375 if (OBJECT_IS_XML(cx, obj)) {
376 if (!js_GetXMLFunction(cx, obj, ATOM_TO_JSID(atom), vp))
377 goto bad;
378 } else
379 #endif
381 if (!OBJ_GET_PROPERTY(cx, obj, ATOM_TO_JSID(atom), vp))
382 goto bad;
385 if (JSVAL_IS_VOID(*vp)) {
386 default_iter:
388 * Fail over to the default enumerating native iterator.
390 * Create iterobj with a NULL parent to ensure that we use the correct
391 * scope chain to lookup the iterator's constructor. Since we use the
392 * parent slot to keep track of the iterable, we must fix it up after.
394 iterobj = js_NewObject(cx, &js_IteratorClass, NULL, NULL);
395 if (!iterobj)
396 goto bad;
398 /* Store iterobj in *vp to protect it from GC (callers must root vp). */
399 *vp = OBJECT_TO_JSVAL(iterobj);
401 if (!InitNativeIterator(cx, iterobj, obj, flags))
402 goto bad;
403 } else {
404 arg = BOOLEAN_TO_JSVAL((flags & JSITER_FOREACH) == 0);
405 if (!js_InternalInvoke(cx, obj, *vp, JSINVOKE_ITERATOR, 1, &arg, vp))
406 goto bad;
407 if (JSVAL_IS_PRIMITIVE(*vp)) {
408 const char *printable = js_AtomToPrintableString(cx, atom);
409 if (printable) {
410 js_ReportValueError2(cx, JSMSG_BAD_ITERATOR_RETURN,
411 JSDVG_SEARCH_STACK, *vp, NULL, printable);
413 goto bad;
417 ok = JS_TRUE;
418 out:
419 if (obj)
420 JS_POP_TEMP_ROOT(cx, &tvr);
421 return ok;
422 bad:
423 ok = JS_FALSE;
424 goto out;
427 JSBool
428 js_CloseIterator(JSContext *cx, jsval v)
430 JSObject *obj;
431 JSClass *clasp;
433 JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
434 obj = JSVAL_TO_OBJECT(v);
435 clasp = OBJ_GET_CLASS(cx, obj);
437 if (clasp == &js_IteratorClass) {
438 js_CloseNativeIterator(cx, obj);
440 #if JS_HAS_GENERATORS
441 else if (clasp == &js_GeneratorClass) {
442 if (!CloseGenerator(cx, obj))
443 return JS_FALSE;
445 #endif
446 return JS_TRUE;
449 static JSBool
450 CallEnumeratorNext(JSContext *cx, JSObject *iterobj, uintN flags, jsval *rval)
452 JSObject *obj, *origobj;
453 jsval state;
454 JSBool foreach;
455 jsid id;
456 JSObject *obj2;
457 JSBool cond;
458 JSClass *clasp;
459 JSExtendedClass *xclasp;
460 JSProperty *prop;
461 JSString *str;
463 JS_ASSERT(flags & JSITER_ENUMERATE);
464 JS_ASSERT(STOBJ_GET_CLASS(iterobj) == &js_IteratorClass);
466 obj = STOBJ_GET_PARENT(iterobj);
467 origobj = STOBJ_GET_PROTO(iterobj);
468 state = STOBJ_GET_SLOT(iterobj, JSSLOT_ITER_STATE);
469 if (JSVAL_IS_NULL(state))
470 goto stop;
472 foreach = (flags & JSITER_FOREACH) != 0;
473 #if JS_HAS_XML_SUPPORT
475 * Treat an XML object specially only when it starts the prototype chain.
476 * Otherwise we need to do the usual deleted and shadowed property checks.
478 if (obj == origobj && OBJECT_IS_XML(cx, obj)) {
479 if (foreach) {
480 JSXMLObjectOps *xmlops = (JSXMLObjectOps *) obj->map->ops;
482 if (!xmlops->enumerateValues(cx, obj, JSENUMERATE_NEXT, &state,
483 &id, rval)) {
484 return JS_FALSE;
486 } else {
487 if (!OBJ_ENUMERATE(cx, obj, JSENUMERATE_NEXT, &state, &id))
488 return JS_FALSE;
490 STOBJ_SET_SLOT(iterobj, JSSLOT_ITER_STATE, state);
491 if (JSVAL_IS_NULL(state))
492 goto stop;
493 } else
494 #endif
496 restart:
497 if (!OBJ_ENUMERATE(cx, obj, JSENUMERATE_NEXT, &state, &id))
498 return JS_TRUE;
500 STOBJ_SET_SLOT(iterobj, JSSLOT_ITER_STATE, state);
501 if (JSVAL_IS_NULL(state)) {
502 #if JS_HAS_XML_SUPPORT
503 if (OBJECT_IS_XML(cx, obj)) {
505 * We just finished enumerating an XML obj that is present on
506 * the prototype chain of a non-XML origobj. Stop further
507 * prototype chain searches because XML objects don't
508 * enumerate prototypes.
510 JS_ASSERT(origobj != obj);
511 JS_ASSERT(!OBJECT_IS_XML(cx, origobj));
512 } else
513 #endif
515 obj = OBJ_GET_PROTO(cx, obj);
516 if (obj) {
517 STOBJ_SET_PARENT(iterobj, obj);
518 if (!OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &state, NULL))
519 return JS_FALSE;
520 STOBJ_SET_SLOT(iterobj, JSSLOT_ITER_STATE, state);
521 if (!JSVAL_IS_NULL(state))
522 goto restart;
525 goto stop;
528 /* Skip properties not in obj when looking from origobj. */
529 if (!OBJ_LOOKUP_PROPERTY(cx, origobj, id, &obj2, &prop))
530 return JS_FALSE;
531 if (!prop)
532 goto restart;
533 OBJ_DROP_PROPERTY(cx, obj2, prop);
536 * If the id was found in a prototype object or an unrelated object
537 * (specifically, not in an inner object for obj), skip it. This step
538 * means that all OBJ_LOOKUP_PROPERTY implementations must return an
539 * object further along on the prototype chain, or else possibly an
540 * object returned by the JSExtendedClass.outerObject optional hook.
542 if (obj != obj2) {
543 cond = JS_FALSE;
544 clasp = OBJ_GET_CLASS(cx, obj2);
545 if (clasp->flags & JSCLASS_IS_EXTENDED) {
546 xclasp = (JSExtendedClass *) clasp;
547 cond = xclasp->outerObject &&
548 xclasp->outerObject(cx, obj2) == obj;
550 if (!cond)
551 goto restart;
554 if (foreach) {
555 /* Get property querying the original object. */
556 if (!OBJ_GET_PROPERTY(cx, origobj, id, rval))
557 return JS_FALSE;
561 if (foreach) {
562 if (flags & JSITER_KEYVALUE) {
563 if (!NewKeyValuePair(cx, id, *rval, rval))
564 return JS_FALSE;
566 } else {
567 /* Make rval a string for uniformity and compatibility. */
568 str = js_ValueToString(cx, ID_TO_VALUE(id));
569 if (!str)
570 return JS_FALSE;
571 *rval = STRING_TO_JSVAL(str);
573 return JS_TRUE;
575 stop:
576 JS_ASSERT(STOBJ_GET_SLOT(iterobj, JSSLOT_ITER_STATE) == JSVAL_NULL);
577 *rval = JSVAL_HOLE;
578 return JS_TRUE;
581 JSBool
582 js_CallIteratorNext(JSContext *cx, JSObject *iterobj, jsval *rval)
584 uintN flags;
586 /* Fast path for native iterators */
587 if (OBJ_GET_CLASS(cx, iterobj) == &js_IteratorClass) {
588 flags = JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
589 if (flags & JSITER_ENUMERATE)
590 return CallEnumeratorNext(cx, iterobj, flags, rval);
593 * Call next directly as all the methods of the native iterator are
594 * read-only and permanent.
596 if (!IteratorNextImpl(cx, iterobj, rval))
597 return JS_FALSE;
598 } else {
599 jsid id = ATOM_TO_JSID(cx->runtime->atomState.nextAtom);
601 if (!JS_GetMethodById(cx, iterobj, id, &iterobj, rval))
602 return JS_FALSE;
603 if (!js_InternalCall(cx, iterobj, *rval, 0, NULL, rval)) {
604 /* Check for StopIteration. */
605 if (!cx->throwing ||
606 JSVAL_IS_PRIMITIVE(cx->exception) ||
607 OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(cx->exception))
608 != &js_StopIterationClass) {
609 return JS_FALSE;
612 /* Inline JS_ClearPendingException(cx). */
613 cx->throwing = JS_FALSE;
614 cx->exception = JSVAL_VOID;
615 *rval = JSVAL_HOLE;
616 return JS_TRUE;
620 return JS_TRUE;
623 static JSBool
624 stopiter_hasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
626 *bp = !JSVAL_IS_PRIMITIVE(v) &&
627 OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v)) == &js_StopIterationClass;
628 return JS_TRUE;
631 JSClass js_StopIterationClass = {
632 js_StopIteration_str,
633 JSCLASS_HAS_CACHED_PROTO(JSProto_StopIteration),
634 JS_PropertyStub, JS_PropertyStub,
635 JS_PropertyStub, JS_PropertyStub,
636 JS_EnumerateStub, JS_ResolveStub,
637 JS_ConvertStub, JS_FinalizeStub,
638 NULL, NULL,
639 NULL, NULL,
640 NULL, stopiter_hasInstance,
641 NULL, NULL
644 #if JS_HAS_GENERATORS
646 static void
647 generator_finalize(JSContext *cx, JSObject *obj)
649 JSGenerator *gen;
651 gen = (JSGenerator *) JS_GetPrivate(cx, obj);
652 if (gen) {
654 * gen can be open on shutdown when close hooks are ignored or when
655 * the embedding cancels scheduled close hooks.
657 JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_CLOSED ||
658 gen->state == JSGEN_OPEN);
659 JS_free(cx, gen);
663 static void
664 generator_trace(JSTracer *trc, JSObject *obj)
666 JSGenerator *gen;
668 gen = (JSGenerator *) JS_GetPrivate(trc->context, obj);
669 if (!gen)
670 return;
673 * js_TraceStackFrame does not recursively trace the down-linked frame
674 * chain, so we insist that gen->frame has no parent to trace when the
675 * generator is not running.
677 JS_ASSERT_IF(gen->state != JSGEN_RUNNING && gen->state != JSGEN_CLOSING,
678 !gen->frame.down);
681 * FIXME be 390950. Generator's frame is a part of the JS stack when the
682 * generator is running or closing. Thus tracing the frame in this case
683 * here duplicates the work done in js_TraceContext.
685 js_TraceStackFrame(trc, &gen->frame);
688 JSClass js_GeneratorClass = {
689 js_Generator_str,
690 JSCLASS_HAS_PRIVATE | JSCLASS_IS_ANONYMOUS |
691 JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_Generator),
692 JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
693 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, generator_finalize,
694 NULL, NULL, NULL, NULL,
695 NULL, NULL, JS_CLASS_TRACE(generator_trace), NULL
699 * Called from the JSOP_GENERATOR case in the interpreter, with fp referring
700 * to the frame by which the generator function was activated. Create a new
701 * JSGenerator object, which contains its own JSStackFrame that we populate
702 * from *fp. We know that upon return, the JSOP_GENERATOR opcode will return
703 * from the activation in fp, so we can steal away fp->callobj and fp->argsobj
704 * if they are non-null.
706 JSObject *
707 js_NewGenerator(JSContext *cx, JSStackFrame *fp)
709 JSObject *obj;
710 uintN argc, nargs, nvars, depth, nslots;
711 JSGenerator *gen;
712 jsval *newsp;
714 /* After the following return, failing control flow must goto bad. */
715 obj = js_NewObject(cx, &js_GeneratorClass, NULL, NULL);
716 if (!obj)
717 return NULL;
719 /* Load and compute stack slot counts. */
720 argc = fp->argc;
721 nargs = JS_MAX(argc, fp->fun->nargs);
722 nvars = fp->nvars;
723 depth = fp->script->depth;
724 nslots = 2 + nargs + nvars + 2 * depth;
726 /* Allocate obj's private data struct. */
727 gen = (JSGenerator *)
728 JS_malloc(cx, sizeof(JSGenerator) + (nslots - 1) * sizeof(jsval));
729 if (!gen)
730 goto bad;
732 gen->obj = obj;
734 /* Steal away objects reflecting fp and point them at gen->frame. */
735 gen->frame.callobj = fp->callobj;
736 if (fp->callobj) {
737 JS_SetPrivate(cx, fp->callobj, &gen->frame);
738 fp->callobj = NULL;
740 gen->frame.argsobj = fp->argsobj;
741 if (fp->argsobj) {
742 JS_SetPrivate(cx, fp->argsobj, &gen->frame);
743 fp->argsobj = NULL;
746 /* These two references can be shared with fp until it goes away. */
747 gen->frame.varobj = fp->varobj;
748 gen->frame.thisp = fp->thisp;
750 /* Copy call-invariant script and function references. */
751 gen->frame.script = fp->script;
752 gen->frame.callee = fp->callee;
753 gen->frame.fun = fp->fun;
755 /* Use newsp to carve space out of gen->stack. */
756 newsp = gen->stack;
757 gen->arena.next = NULL;
758 gen->arena.base = (jsuword) newsp;
759 gen->arena.limit = gen->arena.avail = (jsuword) (newsp + nslots);
761 #define COPY_STACK_ARRAY(vec,cnt,num) \
762 JS_BEGIN_MACRO \
763 gen->frame.cnt = cnt; \
764 gen->frame.vec = newsp; \
765 newsp += (num); \
766 memcpy(gen->frame.vec, fp->vec, (num) * sizeof(jsval)); \
767 JS_END_MACRO
769 /* Copy argv, rval, and vars. */
770 *newsp++ = fp->argv[-2];
771 *newsp++ = fp->argv[-1];
772 COPY_STACK_ARRAY(argv, argc, nargs);
773 gen->frame.rval = fp->rval;
774 COPY_STACK_ARRAY(vars, nvars, nvars);
776 #undef COPY_STACK_ARRAY
778 /* Initialize or copy virtual machine state. */
779 gen->frame.down = NULL;
780 gen->frame.annotation = NULL;
781 gen->frame.scopeChain = fp->scopeChain;
782 gen->frame.pc = fp->pc;
784 /* Allocate generating pc and operand stack space. */
785 gen->frame.spbase = gen->frame.sp = newsp + depth;
787 /* Copy remaining state (XXX sharp* and xml* should be local vars). */
788 gen->frame.sharpDepth = 0;
789 gen->frame.sharpArray = NULL;
790 gen->frame.flags = fp->flags | JSFRAME_GENERATOR;
791 gen->frame.dormantNext = NULL;
792 gen->frame.xmlNamespace = NULL;
793 gen->frame.blockChain = NULL;
795 /* Note that gen is newborn. */
796 gen->state = JSGEN_NEWBORN;
798 if (!JS_SetPrivate(cx, obj, gen)) {
799 JS_free(cx, gen);
800 goto bad;
802 return obj;
804 bad:
805 cx->weakRoots.newborn[GCX_OBJECT] = NULL;
806 return NULL;
809 typedef enum JSGeneratorOp {
810 JSGENOP_NEXT,
811 JSGENOP_SEND,
812 JSGENOP_THROW,
813 JSGENOP_CLOSE
814 } JSGeneratorOp;
817 * Start newborn or restart yielding generator and perform the requested
818 * operation inside its frame.
820 static JSBool
821 SendToGenerator(JSContext *cx, JSGeneratorOp op, JSObject *obj,
822 JSGenerator *gen, jsval arg, jsval *rval)
824 JSStackFrame *fp;
825 jsval junk;
826 JSArena *arena;
827 JSBool ok;
829 if (gen->state == JSGEN_RUNNING || gen->state == JSGEN_CLOSING) {
830 js_ReportValueError(cx, JSMSG_NESTING_GENERATOR,
831 JSDVG_SEARCH_STACK, OBJECT_TO_JSVAL(obj),
832 JS_GetFunctionId(gen->frame.fun));
833 return JS_FALSE;
836 JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_OPEN);
837 switch (op) {
838 case JSGENOP_NEXT:
839 case JSGENOP_SEND:
840 if (gen->state == JSGEN_OPEN) {
842 * Store the argument to send as the result of the yield
843 * expression.
845 gen->frame.sp[-1] = arg;
847 gen->state = JSGEN_RUNNING;
848 break;
850 case JSGENOP_THROW:
851 JS_SetPendingException(cx, arg);
852 gen->state = JSGEN_RUNNING;
853 break;
855 default:
856 JS_ASSERT(op == JSGENOP_CLOSE);
857 JS_SetPendingException(cx, JSVAL_ARETURN);
858 gen->state = JSGEN_CLOSING;
859 break;
862 /* Extend the current stack pool with gen->arena. */
863 arena = cx->stackPool.current;
864 JS_ASSERT(!arena->next);
865 JS_ASSERT(!gen->arena.next);
866 JS_ASSERT(cx->stackPool.current != &gen->arena);
867 cx->stackPool.current = arena->next = &gen->arena;
869 /* Push gen->frame around the interpreter activation. */
870 fp = cx->fp;
871 cx->fp = &gen->frame;
872 gen->frame.down = fp;
873 ok = js_Interpret(cx, gen->frame.pc, &junk);
874 cx->fp = fp;
875 gen->frame.down = NULL;
877 /* Retract the stack pool and sanitize gen->arena. */
878 JS_ASSERT(!gen->arena.next);
879 JS_ASSERT(arena->next == &gen->arena);
880 JS_ASSERT(cx->stackPool.current == &gen->arena);
881 cx->stackPool.current = arena;
882 arena->next = NULL;
884 if (gen->frame.flags & JSFRAME_YIELDING) {
885 /* Yield cannot fail, throw or be called on closing. */
886 JS_ASSERT(ok);
887 JS_ASSERT(!cx->throwing);
888 JS_ASSERT(gen->state == JSGEN_RUNNING);
889 JS_ASSERT(op != JSGENOP_CLOSE);
890 gen->frame.flags &= ~JSFRAME_YIELDING;
891 gen->state = JSGEN_OPEN;
892 *rval = gen->frame.rval;
893 return JS_TRUE;
896 gen->state = JSGEN_CLOSED;
898 if (ok) {
899 /* Returned, explicitly or by falling off the end. */
900 if (op == JSGENOP_CLOSE) {
901 *rval = JSVAL_VOID;
902 return JS_TRUE;
904 return js_ThrowStopIteration(cx, obj);
908 * An error, silent termination by branch callback or an exception.
909 * Propagate the condition to the caller.
911 return JS_FALSE;
914 static JSBool
915 CloseGenerator(JSContext *cx, JSObject *obj)
917 JSGenerator *gen;
918 jsval junk;
920 JS_ASSERT(STOBJ_GET_CLASS(obj) == &js_GeneratorClass);
921 gen = (JSGenerator *) JS_GetPrivate(cx, obj);
922 if (!gen) {
923 /* Generator prototype object. */
924 return JS_TRUE;
927 if (gen->state == JSGEN_CLOSED)
928 return JS_TRUE;
930 /* SendToGenerator always sets *rval to JSVAL_VOID for JSGENOP_CLOSE. */
931 return SendToGenerator(cx, JSGENOP_CLOSE, obj, gen, JSVAL_VOID, &junk);
935 * Common subroutine of generator_(next|send|throw|close) methods.
937 static JSBool
938 generator_op(JSContext *cx, JSGeneratorOp op, jsval *vp)
940 JSObject *obj;
941 JSGenerator *gen;
942 jsval arg;
944 obj = JSVAL_TO_OBJECT(vp[1]);
945 if (!JS_InstanceOf(cx, obj, &js_GeneratorClass, vp + 2))
946 return JS_FALSE;
948 gen = (JSGenerator *) JS_GetPrivate(cx, obj);
949 if (gen == NULL) {
950 /* This happens when obj is the generator prototype. See bug 352885. */
951 goto closed_generator;
954 if (gen->state == JSGEN_NEWBORN) {
955 switch (op) {
956 case JSGENOP_NEXT:
957 case JSGENOP_THROW:
958 break;
960 case JSGENOP_SEND:
961 if (!JSVAL_IS_VOID(vp[2])) {
962 js_ReportValueError(cx, JSMSG_BAD_GENERATOR_SEND,
963 JSDVG_SEARCH_STACK, vp[2], NULL);
964 return JS_FALSE;
966 break;
968 default:
969 JS_ASSERT(op == JSGENOP_CLOSE);
970 gen->state = JSGEN_CLOSED;
971 return JS_TRUE;
973 } else if (gen->state == JSGEN_CLOSED) {
974 closed_generator:
975 switch (op) {
976 case JSGENOP_NEXT:
977 case JSGENOP_SEND:
978 return js_ThrowStopIteration(cx, obj);
979 case JSGENOP_THROW:
980 JS_SetPendingException(cx, vp[2]);
981 return JS_FALSE;
982 default:
983 JS_ASSERT(op == JSGENOP_CLOSE);
984 return JS_TRUE;
988 arg = (op == JSGENOP_SEND || op == JSGENOP_THROW)
989 ? vp[2]
990 : JSVAL_VOID;
991 if (!SendToGenerator(cx, op, obj, gen, arg, vp))
992 return JS_FALSE;
993 return JS_TRUE;
996 static JSBool
997 generator_send(JSContext *cx, uintN argc, jsval *vp)
999 return generator_op(cx, JSGENOP_SEND, vp);
1002 static JSBool
1003 generator_next(JSContext *cx, uintN argc, jsval *vp)
1005 return generator_op(cx, JSGENOP_NEXT, vp);
1008 static JSBool
1009 generator_throw(JSContext *cx, uintN argc, jsval *vp)
1011 return generator_op(cx, JSGENOP_THROW, vp);
1014 static JSBool
1015 generator_close(JSContext *cx, uintN argc, jsval *vp)
1017 return generator_op(cx, JSGENOP_CLOSE, vp);
1020 static JSFunctionSpec generator_methods[] = {
1021 JS_FN(js_iterator_str, iterator_self, 0,0,JSPROP_ROPERM,0),
1022 JS_FN(js_next_str, generator_next, 0,0,JSPROP_ROPERM,0),
1023 JS_FN(js_send_str, generator_send, 1,1,JSPROP_ROPERM,0),
1024 JS_FN(js_throw_str, generator_throw, 1,1,JSPROP_ROPERM,0),
1025 JS_FN(js_close_str, generator_close, 0,0,JSPROP_ROPERM,0),
1026 JS_FS_END
1029 #endif /* JS_HAS_GENERATORS */
1031 JSObject *
1032 js_InitIteratorClasses(JSContext *cx, JSObject *obj)
1034 JSObject *proto, *stop;
1036 /* Idempotency required: we initialize several things, possibly lazily. */
1037 if (!js_GetClassObject(cx, obj, JSProto_StopIteration, &stop))
1038 return NULL;
1039 if (stop)
1040 return stop;
1042 proto = JS_InitClass(cx, obj, NULL, &js_IteratorClass, Iterator, 2,
1043 NULL, iterator_methods, NULL, NULL);
1044 if (!proto)
1045 return NULL;
1046 STOBJ_SET_SLOT(proto, JSSLOT_ITER_STATE, JSVAL_NULL);
1048 #if JS_HAS_GENERATORS
1049 /* Initialize the generator internals if configured. */
1050 if (!JS_InitClass(cx, obj, NULL, &js_GeneratorClass, NULL, 0,
1051 NULL, generator_methods, NULL, NULL)) {
1052 return NULL;
1054 #endif
1056 return JS_InitClass(cx, obj, NULL, &js_StopIterationClass, NULL, 0,
1057 NULL, NULL, NULL, NULL);