Imported Upstream version 1.0.0
[platform/upstream/js.git] / js / src / jsiter.cpp
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sw=4 et tw=78:
3  *
4  * ***** BEGIN LICENSE BLOCK *****
5  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6  *
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/
11  *
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.
16  *
17  * The Original Code is Mozilla Communicator client code, released
18  * March 31, 1998.
19  *
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.
24  *
25  * Contributor(s):
26  *
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.
38  *
39  * ***** END LICENSE BLOCK ***** */
40
41 /*
42  * JavaScript iterators.
43  */
44 #include <string.h>     /* for memcpy */
45 #include "jstypes.h"
46 #include "jsstdint.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 "jsbuiltins.h"
54 #include "jscntxt.h"
55 #include "jsversion.h"
56 #include "jsexn.h"
57 #include "jsfun.h"
58 #include "jsgc.h"
59 #include "jshashtable.h"
60 #include "jsinterp.h"
61 #include "jsiter.h"
62 #include "jslock.h"
63 #include "jsnum.h"
64 #include "jsobj.h"
65 #include "jsopcode.h"
66 #include "jsproxy.h"
67 #include "jsscan.h"
68 #include "jsscope.h"
69 #include "jsscript.h"
70 #include "jsstaticcheck.h"
71 #include "jstracer.h"
72 #include "jsvector.h"
73
74 #if JS_HAS_XML_SUPPORT
75 #include "jsxml.h"
76 #endif
77
78 #include "jscntxtinlines.h"
79 #include "jsinterpinlines.h"
80 #include "jsobjinlines.h"
81 #include "jsstrinlines.h"
82
83 using namespace js;
84 using namespace js::gc;
85
86 static void iterator_finalize(JSContext *cx, JSObject *obj);
87 static void iterator_trace(JSTracer *trc, JSObject *obj);
88 static JSObject *iterator_iterator(JSContext *cx, JSObject *obj, JSBool keysonly);
89
90 Class js_IteratorClass = {
91     "Iterator",
92     JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator) |
93     JSCLASS_MARK_IS_TRACE,
94     PropertyStub,         /* addProperty */
95     PropertyStub,         /* delProperty */
96     PropertyStub,         /* getProperty */
97     StrictPropertyStub,   /* setProperty */
98     EnumerateStub,
99     ResolveStub,
100     ConvertStub,
101     iterator_finalize,
102     NULL,                 /* reserved    */
103     NULL,                 /* checkAccess */
104     NULL,                 /* call        */
105     NULL,                 /* construct   */
106     NULL,                 /* xdrObject   */
107     NULL,                 /* hasInstance */
108     JS_CLASS_TRACE(iterator_trace),
109     {
110         NULL,             /* equality       */
111         NULL,             /* outerObject    */
112         NULL,             /* innerObject    */
113         iterator_iterator,
114         NULL              /* unused  */
115     }
116 };
117
118 void
119 NativeIterator::mark(JSTracer *trc)
120 {
121     MarkIdRange(trc, begin(), end(), "props");
122     if (obj)
123         MarkObject(trc, *obj, "obj");
124 }
125
126 static void
127 iterator_finalize(JSContext *cx, JSObject *obj)
128 {
129     JS_ASSERT(obj->getClass() == &js_IteratorClass);
130
131     NativeIterator *ni = obj->getNativeIterator();
132     if (ni) {
133         cx->free(ni);
134         obj->setNativeIterator(NULL);
135     }
136 }
137
138 static void
139 iterator_trace(JSTracer *trc, JSObject *obj)
140 {
141     NativeIterator *ni = obj->getNativeIterator();
142
143     if (ni)
144         ni->mark(trc);
145 }
146
147 struct IdHashPolicy {
148     typedef jsid Lookup;
149     static HashNumber hash(jsid id) {
150         return JSID_BITS(id);
151     }
152     static bool match(jsid id1, jsid id2) {
153         return id1 == id2;
154     }
155 };
156
157 typedef HashSet<jsid, IdHashPolicy, ContextAllocPolicy> IdSet;
158
159 static inline bool
160 NewKeyValuePair(JSContext *cx, jsid id, const Value &val, Value *rval)
161 {
162     Value vec[2] = { IdToValue(id), val };
163     AutoArrayRooter tvr(cx, JS_ARRAY_LENGTH(vec), vec);
164
165     JSObject *aobj = NewDenseCopiedArray(cx, 2, vec);
166     if (!aobj)
167         return false;
168     rval->setObject(*aobj);
169     return true;
170 }
171
172 static inline bool
173 Enumerate(JSContext *cx, JSObject *obj, JSObject *pobj, jsid id,
174           bool enumerable, bool sharedPermanent, uintN flags, IdSet& ht,
175           AutoIdVector *props)
176 {
177     IdSet::AddPtr p = ht.lookupForAdd(id);
178     JS_ASSERT_IF(obj == pobj && !obj->isProxy(), !p);
179
180     /* If we've already seen this, we definitely won't add it. */
181     if (JS_UNLIKELY(!!p))
182         return true;
183
184     /*
185      * It's not necessary to add properties to the hash table at the end of the
186      * prototype chain -- but a proxy might return duplicated properties, so
187      * always add for them.
188      */
189     if ((pobj->getProto() || pobj->isProxy()) && !ht.add(p, id))
190         return false;
191
192     if (JS_UNLIKELY(flags & JSITER_OWNONLY)) {
193         /*
194          * Shared-permanent hack: If this property is shared permanent
195          * and pobj and obj have the same class, then treat it as an own
196          * property of obj, even if pobj != obj. (But see bug 575997.)
197          *
198          * Omit the magic __proto__ property so that JS code can use
199          * Object.getOwnPropertyNames without worrying about it.
200          */
201         if (!pobj->getProto() && id == ATOM_TO_JSID(cx->runtime->atomState.protoAtom))
202             return true;
203         if (pobj != obj && !(sharedPermanent && pobj->getClass() == obj->getClass()))
204             return true;
205     }
206
207     if (enumerable || (flags & JSITER_HIDDEN))
208         return props->append(id);
209
210     return true;
211 }
212
213 static bool
214 EnumerateNativeProperties(JSContext *cx, JSObject *obj, JSObject *pobj, uintN flags, IdSet &ht,
215                           AutoIdVector *props)
216 {
217     size_t initialLength = props->length();
218
219     /* Collect all unique properties from this object's scope. */
220     for (Shape::Range r = pobj->lastProperty()->all(); !r.empty(); r.popFront()) {
221         const Shape &shape = r.front();
222
223         if (!JSID_IS_DEFAULT_XML_NAMESPACE(shape.id) &&
224             !shape.isAlias() &&
225             !Enumerate(cx, obj, pobj, shape.id, shape.enumerable(),
226                        shape.isSharedPermanent(), flags, ht, props))
227         {
228             return false;
229         }
230     }
231
232     Reverse(props->begin() + initialLength, props->end());
233     return true;
234 }
235
236 static bool
237 EnumerateDenseArrayProperties(JSContext *cx, JSObject *obj, JSObject *pobj, uintN flags,
238                               IdSet &ht, AutoIdVector *props)
239 {
240     if (!Enumerate(cx, obj, pobj, ATOM_TO_JSID(cx->runtime->atomState.lengthAtom), false, true,
241                    flags, ht, props)) {
242         return false;
243     }
244
245     if (pobj->getArrayLength() > 0) {
246         size_t capacity = pobj->getDenseArrayCapacity();
247         Value *vp = pobj->getDenseArrayElements();
248         for (size_t i = 0; i < capacity; ++i, ++vp) {
249             if (!vp->isMagic(JS_ARRAY_HOLE)) {
250                 /* Dense arrays never get so large that i would not fit into an integer id. */
251                 if (!Enumerate(cx, obj, pobj, INT_TO_JSID(i), true, false, flags, ht, props))
252                     return false;
253             }
254         }
255     }
256
257     return true;
258 }
259
260 static bool
261 Snapshot(JSContext *cx, JSObject *obj, uintN flags, AutoIdVector *props)
262 {
263     /*
264      * FIXME: Bug 575997 - We won't need to initialize this hash table if
265      *        (flags & JSITER_OWNONLY) when we eliminate inheritance of
266      *        shared-permanent properties as own properties.
267      */
268     IdSet ht(cx);
269     if (!ht.init(32))
270         return NULL;
271
272     JSObject *pobj = obj;
273     do {
274         Class *clasp = pobj->getClass();
275         if (pobj->isNative() &&
276             !pobj->getOps()->enumerate &&
277             !(clasp->flags & JSCLASS_NEW_ENUMERATE)) {
278             if (!clasp->enumerate(cx, pobj))
279                 return false;
280             if (!EnumerateNativeProperties(cx, obj, pobj, flags, ht, props))
281                 return false;
282         } else if (pobj->isDenseArray()) {
283             if (!EnumerateDenseArrayProperties(cx, obj, pobj, flags, ht, props))
284                 return false;
285         } else {
286             if (pobj->isProxy()) {
287                 AutoIdVector proxyProps(cx);
288                 if (flags & JSITER_OWNONLY) {
289                     if (flags & JSITER_HIDDEN) {
290                         if (!JSProxy::getOwnPropertyNames(cx, pobj, proxyProps))
291                             return false;
292                     } else {
293                         if (!JSProxy::keys(cx, pobj, proxyProps))
294                             return false;
295                     }
296                 } else {
297                     if (!JSProxy::enumerate(cx, pobj, proxyProps))
298                         return false;
299                 }
300                 for (size_t n = 0, len = proxyProps.length(); n < len; n++) {
301                     if (!Enumerate(cx, obj, pobj, proxyProps[n], true, false, flags, ht, props))
302                         return false;
303                 }
304                 /* Proxy objects enumerate the prototype on their own, so we are done here. */
305                 break;
306             }
307             Value state;
308             JSIterateOp op = (flags & JSITER_HIDDEN) ? JSENUMERATE_INIT_ALL : JSENUMERATE_INIT;
309             if (!pobj->enumerate(cx, op, &state, NULL))
310                 return false;
311             if (state.isMagic(JS_NATIVE_ENUMERATE)) {
312                 if (!EnumerateNativeProperties(cx, obj, pobj, flags, ht, props))
313                     return false;
314             } else {
315                 while (true) {
316                     jsid id;
317                     if (!pobj->enumerate(cx, JSENUMERATE_NEXT, &state, &id))
318                         return false;
319                     if (state.isNull())
320                         break;
321                     if (!Enumerate(cx, obj, pobj, id, true, false, flags, ht, props))
322                         return false;
323                 }
324             }
325         }
326
327         if (JS_UNLIKELY(pobj->isXML()))
328             break;
329     } while ((pobj = pobj->getProto()) != NULL);
330
331     return true;
332 }
333
334 namespace js {
335
336 bool
337 VectorToIdArray(JSContext *cx, AutoIdVector &props, JSIdArray **idap)
338 {
339     JS_STATIC_ASSERT(sizeof(JSIdArray) > sizeof(jsid));
340     size_t len = props.length();
341     size_t idsz = len * sizeof(jsid);
342     size_t sz = (sizeof(JSIdArray) - sizeof(jsid)) + idsz;
343     JSIdArray *ida = static_cast<JSIdArray *>(cx->malloc(sz));
344     if (!ida)
345         return false;
346
347     ida->length = static_cast<jsint>(len);
348     memcpy(ida->vector, props.begin(), idsz);
349     *idap = ida;
350     return true;
351 }
352
353 JS_FRIEND_API(bool)
354 GetPropertyNames(JSContext *cx, JSObject *obj, uintN flags, AutoIdVector *props)
355 {
356     return Snapshot(cx, obj, flags & (JSITER_OWNONLY | JSITER_HIDDEN), props);
357 }
358
359 }
360
361 static inline bool
362 GetCustomIterator(JSContext *cx, JSObject *obj, uintN flags, Value *vp)
363 {
364     /* Check whether we have a valid __iterator__ method. */
365     JSAtom *atom = cx->runtime->atomState.iteratorAtom;
366     if (!js_GetMethod(cx, obj, ATOM_TO_JSID(atom), JSGET_NO_METHOD_BARRIER, vp))
367         return false;
368
369     /* If there is no custom __iterator__ method, we are done here. */
370     if (!vp->isObject()) {
371         vp->setUndefined();
372         return true;
373     }
374
375     /* Otherwise call it and return that object. */
376     LeaveTrace(cx);
377     Value arg = BooleanValue((flags & JSITER_FOREACH) == 0);
378     if (!ExternalInvoke(cx, ObjectValue(*obj), *vp, 1, &arg, vp))
379         return false;
380     if (vp->isPrimitive()) {
381         /*
382          * We are always coming from js_ValueToIterator, and we are no longer on
383          * trace, so the object we are iterating over is on top of the stack (-1).
384          */
385         JSAutoByteString bytes;
386         if (!js_AtomToPrintableString(cx, atom, &bytes))
387             return false;
388         js_ReportValueError2(cx, JSMSG_BAD_TRAP_RETURN_VALUE,
389                              -1, ObjectValue(*obj), NULL, bytes.ptr());
390         return false;
391     }
392     return true;
393 }
394
395 template <typename T>
396 static inline bool
397 Compare(T *a, T *b, size_t c)
398 {
399     size_t n = (c + size_t(7)) / size_t(8);
400     switch (c % 8) {
401       case 0: do { if (*a++ != *b++) return false;
402       case 7:      if (*a++ != *b++) return false;
403       case 6:      if (*a++ != *b++) return false;
404       case 5:      if (*a++ != *b++) return false;
405       case 4:      if (*a++ != *b++) return false;
406       case 3:      if (*a++ != *b++) return false;
407       case 2:      if (*a++ != *b++) return false;
408       case 1:      if (*a++ != *b++) return false;
409               } while (--n > 0);
410     }
411     return true;
412 }
413
414 static inline JSObject *
415 NewIteratorObject(JSContext *cx, uintN flags)
416 {
417     if (flags & JSITER_ENUMERATE) {
418         /*
419          * Non-escaping native enumerator objects do not need map, proto, or
420          * parent. However, code in jstracer.cpp and elsewhere may find such a
421          * native enumerator object via the stack and (as for all objects that
422          * are not stillborn, with the exception of "NoSuchMethod" internal
423          * helper objects) expect it to have a non-null map pointer, so we
424          * share an empty Enumerator scope in the runtime.
425          */
426         JSObject *obj = js_NewGCObject(cx, FINALIZE_OBJECT0);
427         if (!obj)
428             return false;
429         obj->init(cx, &js_IteratorClass, NULL, NULL, NULL, false);
430         obj->setMap(cx->compartment->emptyEnumeratorShape);
431         return obj;
432     }
433
434     return NewBuiltinClassInstance(cx, &js_IteratorClass);
435 }
436
437 NativeIterator *
438 NativeIterator::allocateIterator(JSContext *cx, uint32 slength, const AutoIdVector &props)
439 {
440     size_t plength = props.length();
441     NativeIterator *ni = (NativeIterator *)
442         cx->malloc(sizeof(NativeIterator) + plength * sizeof(jsid) + slength * sizeof(uint32));
443     if (!ni)
444         return NULL;
445     ni->props_array = ni->props_cursor = (jsid *) (ni + 1);
446     ni->props_end = (jsid *)ni->props_array + plength;
447     if (plength)
448         memcpy(ni->props_array, props.begin(), plength * sizeof(jsid));
449     return ni;
450 }
451
452 inline void
453 NativeIterator::init(JSObject *obj, uintN flags, uint32 slength, uint32 key)
454 {
455     this->obj = obj;
456     this->flags = flags;
457     this->shapes_array = (uint32 *) this->props_end;
458     this->shapes_length = slength;
459     this->shapes_key = key;
460 }
461
462 static inline void
463 RegisterEnumerator(JSContext *cx, JSObject *iterobj, NativeIterator *ni)
464 {
465     /* Register non-escaping native enumerators (for-in) with the current context. */
466     if (ni->flags & JSITER_ENUMERATE) {
467         ni->next = cx->enumerators;
468         cx->enumerators = iterobj;
469
470         JS_ASSERT(!(ni->flags & JSITER_ACTIVE));
471         ni->flags |= JSITER_ACTIVE;
472     }
473 }
474
475 static inline bool
476 VectorToKeyIterator(JSContext *cx, JSObject *obj, uintN flags, AutoIdVector &keys,
477                     uint32 slength, uint32 key, Value *vp)
478 {
479     JS_ASSERT(!(flags & JSITER_FOREACH));
480
481     JSObject *iterobj = NewIteratorObject(cx, flags);
482     if (!iterobj)
483         return false;
484
485     NativeIterator *ni = NativeIterator::allocateIterator(cx, slength, keys);
486     if (!ni)
487         return false;
488     ni->init(obj, flags, slength, key);
489
490     if (slength) {
491         /*
492          * Fill in the shape array from scratch.  We can't use the array that was
493          * computed for the cache lookup earlier, as constructing iterobj could
494          * have triggered a shape-regenerating GC.  Don't bother with regenerating
495          * the shape key; if such a GC *does* occur, we can only get hits through
496          * the one-slot lastNativeIterator cache.
497          */
498         JSObject *pobj = obj;
499         size_t ind = 0;
500         do {
501             ni->shapes_array[ind++] = pobj->shape();
502             pobj = pobj->getProto();
503         } while (pobj);
504         JS_ASSERT(ind == slength);
505     }
506
507     iterobj->setNativeIterator(ni);
508     vp->setObject(*iterobj);
509
510     RegisterEnumerator(cx, iterobj, ni);
511     return true;
512 }
513
514 namespace js {
515
516 bool
517 VectorToKeyIterator(JSContext *cx, JSObject *obj, uintN flags, AutoIdVector &props, Value *vp)
518 {
519     return VectorToKeyIterator(cx, obj, flags, props, 0, 0, vp);
520 }
521
522 bool
523 VectorToValueIterator(JSContext *cx, JSObject *obj, uintN flags, AutoIdVector &keys,
524                       Value *vp)
525 {
526     JS_ASSERT(flags & JSITER_FOREACH);
527
528     JSObject *iterobj = NewIteratorObject(cx, flags);
529     if (!iterobj)
530         return false;
531
532     NativeIterator *ni = NativeIterator::allocateIterator(cx, 0, keys);
533     if (!ni)
534         return false;
535     ni->init(obj, flags, 0, 0);
536
537     iterobj->setNativeIterator(ni);
538     vp->setObject(*iterobj);
539
540     RegisterEnumerator(cx, iterobj, ni);
541     return true;
542 }
543
544 bool
545 EnumeratedIdVectorToIterator(JSContext *cx, JSObject *obj, uintN flags, AutoIdVector &props, Value *vp)
546 {
547     if (!(flags & JSITER_FOREACH))
548         return VectorToKeyIterator(cx, obj, flags, props, vp);
549
550     return VectorToValueIterator(cx, obj, flags, props, vp);
551 }
552
553 typedef Vector<uint32, 8> ShapeVector;
554
555 static inline void
556 UpdateNativeIterator(NativeIterator *ni, JSObject *obj)
557 {
558     // Update the object for which the native iterator is associated, so
559     // SuppressDeletedPropertyHelper will recognize the iterator as a match.
560     ni->obj = obj;
561 }
562
563 bool
564 GetIterator(JSContext *cx, JSObject *obj, uintN flags, Value *vp)
565 {
566     Vector<uint32, 8> shapes(cx);
567     uint32 key = 0;
568
569     bool keysOnly = (flags == JSITER_ENUMERATE);
570
571     if (obj) {
572         /* Enumerate Iterator.prototype directly. */
573         JSIteratorOp op = obj->getClass()->ext.iteratorObject;
574         if (op && (obj->getClass() != &js_IteratorClass || obj->getNativeIterator())) {
575             JSObject *iterobj = op(cx, obj, !(flags & JSITER_FOREACH));
576             if (!iterobj)
577                 return false;
578             vp->setObject(*iterobj);
579             return true;
580         }
581
582         if (keysOnly) {
583             /*
584              * Check to see if this is the same as the most recent object which
585              * was iterated over.  We don't explicitly check for shapeless
586              * objects here, as they are not inserted into the cache and
587              * will result in a miss.
588              */
589             JSObject *last = cx->compartment->nativeIterCache.last;
590             JSObject *proto = obj->getProto();
591             if (last) {
592                 NativeIterator *lastni = last->getNativeIterator();
593                 if (!(lastni->flags & (JSITER_ACTIVE|JSITER_UNREUSABLE)) &&
594                     obj->isNative() && 
595                     obj->shape() == lastni->shapes_array[0] &&
596                     proto && proto->isNative() && 
597                     proto->shape() == lastni->shapes_array[1] &&
598                     !proto->getProto()) {
599                     vp->setObject(*last);
600                     UpdateNativeIterator(lastni, obj);
601                     RegisterEnumerator(cx, last, lastni);
602                     return true;
603                 }
604             }
605
606             /*
607              * The iterator object for JSITER_ENUMERATE never escapes, so we
608              * don't care for the proper parent/proto to be set. This also
609              * allows us to re-use a previous iterator object that is not
610              * currently active.
611              */
612             JSObject *pobj = obj;
613             do {
614                 if (!pobj->isNative() ||
615                     obj->getOps()->enumerate ||
616                     pobj->getClass()->enumerate != JS_EnumerateStub) {
617                     shapes.clear();
618                     goto miss;
619                 }
620                 uint32 shape = pobj->shape();
621                 key = (key + (key << 16)) ^ shape;
622                 if (!shapes.append(shape))
623                     return false;
624                 pobj = pobj->getProto();
625             } while (pobj);
626
627             JSObject *iterobj = cx->compartment->nativeIterCache.get(key);
628             if (iterobj) {
629                 NativeIterator *ni = iterobj->getNativeIterator();
630                 if (!(ni->flags & (JSITER_ACTIVE|JSITER_UNREUSABLE)) &&
631                     ni->shapes_key == key &&
632                     ni->shapes_length == shapes.length() &&
633                     Compare(ni->shapes_array, shapes.begin(), ni->shapes_length)) {
634                     vp->setObject(*iterobj);
635
636                     UpdateNativeIterator(ni, obj);
637                     RegisterEnumerator(cx, iterobj, ni);
638                     if (shapes.length() == 2)
639                         cx->compartment->nativeIterCache.last = iterobj;
640                     return true;
641                 }
642             }
643         }
644
645       miss:
646         if (obj->isProxy())
647             return JSProxy::iterate(cx, obj, flags, vp);
648         if (!GetCustomIterator(cx, obj, flags, vp))
649             return false;
650         if (!vp->isUndefined())
651             return true;
652     }
653
654     /* NB: for (var p in null) succeeds by iterating over no properties. */
655
656     AutoIdVector keys(cx);
657     if (flags & JSITER_FOREACH) {
658         if (JS_LIKELY(obj != NULL) && !Snapshot(cx, obj, flags, &keys))
659             return false;
660         JS_ASSERT(shapes.empty());
661         if (!VectorToValueIterator(cx, obj, flags, keys, vp))
662             return false;
663     } else {
664         if (JS_LIKELY(obj != NULL) && !Snapshot(cx, obj, flags, &keys))
665             return false;
666         if (!VectorToKeyIterator(cx, obj, flags, keys, shapes.length(), key, vp))
667             return false;
668     }
669
670     JSObject *iterobj = &vp->toObject();
671
672     /* Cache the iterator object if possible. */
673     if (shapes.length())
674         cx->compartment->nativeIterCache.set(key, iterobj);
675
676     if (shapes.length() == 2)
677         cx->compartment->nativeIterCache.last = iterobj;
678     return true;
679 }
680
681 }
682
683 static JSObject *
684 iterator_iterator(JSContext *cx, JSObject *obj, JSBool keysonly)
685 {
686     return obj;
687 }
688
689 static JSBool
690 Iterator(JSContext *cx, uintN argc, Value *vp)
691 {
692     Value *argv = JS_ARGV(cx, vp);
693     bool keyonly = argc >= 2 ? js_ValueToBoolean(argv[1]) : false;
694     uintN flags = JSITER_OWNONLY | (keyonly ? 0 : (JSITER_FOREACH | JSITER_KEYVALUE));
695     *vp = argc >= 1 ? argv[0] : UndefinedValue();
696     return js_ValueToIterator(cx, flags, vp);
697 }
698
699 JSBool
700 js_ThrowStopIteration(JSContext *cx)
701 {
702     Value v;
703
704     JS_ASSERT(!JS_IsExceptionPending(cx));
705     if (js_FindClassObject(cx, NULL, JSProto_StopIteration, &v))
706         cx->setPendingException(v);
707     return JS_FALSE;
708 }
709
710 static JSBool
711 iterator_next(JSContext *cx, uintN argc, Value *vp)
712 {
713     JSObject *obj = ToObject(cx, &vp[1]);
714     if (!obj || !InstanceOf(cx, obj, &js_IteratorClass, vp + 2))
715         return false;
716
717     if (!js_IteratorMore(cx, obj, vp))
718         return false;
719     if (!vp->toBoolean()) {
720         js_ThrowStopIteration(cx);
721         return false;
722     }
723     return js_IteratorNext(cx, obj, vp);
724 }
725
726 #define JSPROP_ROPERM   (JSPROP_READONLY | JSPROP_PERMANENT)
727
728 static JSFunctionSpec iterator_methods[] = {
729     JS_FN(js_next_str,      iterator_next,  0,JSPROP_ROPERM),
730     JS_FS_END
731 };
732
733 /*
734  * Call ToObject(v).__iterator__(keyonly) if ToObject(v).__iterator__ exists.
735  * Otherwise construct the default iterator.
736  */
737 JS_FRIEND_API(JSBool)
738 js_ValueToIterator(JSContext *cx, uintN flags, Value *vp)
739 {
740     /* JSITER_KEYVALUE must always come with JSITER_FOREACH */
741     JS_ASSERT_IF(flags & JSITER_KEYVALUE, flags & JSITER_FOREACH);
742
743     /*
744      * Make sure the more/next state machine doesn't get stuck. A value might be
745      * left in iterValue when a trace is left due to an operation time-out after
746      * JSOP_MOREITER but before the value is picked up by FOR*.
747      */
748     cx->iterValue.setMagic(JS_NO_ITER_VALUE);
749
750     JSObject *obj;
751     if (vp->isObject()) {
752         /* Common case. */
753         obj = &vp->toObject();
754     } else {
755         /*
756          * Enumerating over null and undefined gives an empty enumerator.
757          * This is contrary to ECMA-262 9.9 ToObject, invoked from step 3 of
758          * the first production in 12.6.4 and step 4 of the second production,
759          * but it's "web JS" compatible. ES5 fixed for-in to match this de-facto
760          * standard.
761          */
762         if ((flags & JSITER_ENUMERATE)) {
763             if (!js_ValueToObjectOrNull(cx, *vp, &obj))
764                 return false;
765             /* fall through */
766         } else {
767             obj = js_ValueToNonNullObject(cx, *vp);
768             if (!obj)
769                 return false;
770         }
771     }
772
773     return GetIterator(cx, obj, flags, vp);
774 }
775
776 #if JS_HAS_GENERATORS
777 static JS_REQUIRES_STACK JSBool
778 CloseGenerator(JSContext *cx, JSObject *genobj);
779 #endif
780
781 JS_FRIEND_API(JSBool)
782 js_CloseIterator(JSContext *cx, JSObject *obj)
783 {
784     cx->iterValue.setMagic(JS_NO_ITER_VALUE);
785
786     Class *clasp = obj->getClass();
787     if (clasp == &js_IteratorClass) {
788         /* Remove enumerators from the active list, which is a stack. */
789         NativeIterator *ni = obj->getNativeIterator();
790
791         if (ni->flags & JSITER_ENUMERATE) {
792             JS_ASSERT(cx->enumerators == obj);
793             cx->enumerators = ni->next;
794
795             JS_ASSERT(ni->flags & JSITER_ACTIVE);
796             ni->flags &= ~JSITER_ACTIVE;
797
798             /*
799              * Reset the enumerator; it may still be in the cached iterators
800              * for this thread, and can be reused.
801              */
802             ni->props_cursor = ni->props_array;
803         }
804     }
805 #if JS_HAS_GENERATORS
806     else if (clasp == &js_GeneratorClass) {
807         return CloseGenerator(cx, obj);
808     }
809 #endif
810     return JS_TRUE;
811 }
812
813 /*
814  * Suppress enumeration of deleted properties. This function must be called
815  * when a property is deleted and there might be active enumerators. 
816  *
817  * We maintain a list of active non-escaping for-in enumerators. To suppress
818  * a property, we check whether each active enumerator contains the (obj, id)
819  * pair and has not yet enumerated |id|. If so, and |id| is the next property,
820  * we simply advance the cursor. Otherwise, we delete |id| from the list.
821  *
822  * We do not suppress enumeration of a property deleted along an object's
823  * prototype chain. Only direct deletions on the object are handled.
824  *
825  * This function can suppress multiple properties at once. The |predicate|
826  * argument is an object which can be called on an id and returns true or
827  * false. It also must have a method |matchesAtMostOne| which allows us to
828  * stop searching after the first deletion if true.
829  */
830 template<typename IdPredicate>
831 static bool
832 SuppressDeletedPropertyHelper(JSContext *cx, JSObject *obj, IdPredicate predicate)
833 {
834     JSObject *iterobj = cx->enumerators;
835     while (iterobj) {
836       again:
837         NativeIterator *ni = iterobj->getNativeIterator();
838         /* This only works for identified surpressed keys, not values. */
839         if (ni->isKeyIter() && ni->obj == obj && ni->props_cursor < ni->props_end) {
840             /* Check whether id is still to come. */
841             jsid *props_cursor = ni->current();
842             jsid *props_end = ni->end();
843             for (jsid *idp = props_cursor; idp < props_end; ++idp) {
844                 if (predicate(*idp)) {
845                     /*
846                      * Check whether another property along the prototype chain
847                      * became visible as a result of this deletion.
848                      */
849                     if (obj->getProto()) {
850                         AutoObjectRooter proto(cx, obj->getProto());
851                         AutoObjectRooter obj2(cx);
852                         JSProperty *prop;
853                         if (!proto.object()->lookupProperty(cx, *idp, obj2.addr(), &prop))
854                             return false;
855                         if (prop) {
856                             uintN attrs;
857                             if (obj2.object()->isNative())
858                                 attrs = ((Shape *) prop)->attributes();
859                             else if (!obj2.object()->getAttributes(cx, *idp, &attrs))
860                                 return false;
861
862                             if (attrs & JSPROP_ENUMERATE)
863                                 continue;
864                         }
865                     }
866
867                     /*
868                      * If lookupProperty or getAttributes above removed a property from
869                      * ni, start over.
870                      */
871                     if (props_end != ni->props_end || props_cursor != ni->props_cursor)
872                         goto again;
873
874                     /*
875                      * No property along the prototype chain stepped in to take the
876                      * property's place, so go ahead and delete id from the list.
877                      * If it is the next property to be enumerated, just skip it.
878                      */
879                     if (idp == props_cursor) {
880                         ni->incCursor();
881                     } else {
882                         memmove(idp, idp + 1, (props_end - (idp + 1)) * sizeof(jsid));
883                         ni->props_end = ni->end() - 1;
884                     }
885
886                     /* Don't reuse modified native iterators. */
887                     ni->flags |= JSITER_UNREUSABLE;
888
889                     if (predicate.matchesAtMostOne())
890                         break;
891                 }
892             }
893         }
894         iterobj = ni->next;
895     }
896     return true;
897 }
898
899 class SingleIdPredicate {
900     jsid id;
901 public:
902     SingleIdPredicate(jsid id) : id(id) {}
903
904     bool operator()(jsid id) { return id == this->id; }
905     bool matchesAtMostOne() { return true; }
906 };
907
908 bool
909 js_SuppressDeletedProperty(JSContext *cx, JSObject *obj, jsid id)
910 {
911     id = js_CheckForStringIndex(id);
912     return SuppressDeletedPropertyHelper(cx, obj, SingleIdPredicate(id));
913 }
914
915 class IndexRangePredicate {
916     jsint begin, end;
917 public:
918     IndexRangePredicate(jsint begin, jsint end) : begin(begin), end(end) {}
919
920     bool operator()(jsid id) { 
921         return JSID_IS_INT(id) && begin <= JSID_TO_INT(id) && JSID_TO_INT(id) < end;
922     }
923     bool matchesAtMostOne() { return false; }
924 };
925
926 bool
927 js_SuppressDeletedIndexProperties(JSContext *cx, JSObject *obj, jsint begin, jsint end)
928 {
929     return SuppressDeletedPropertyHelper(cx, obj, IndexRangePredicate(begin, end));
930 }
931
932 JSBool
933 js_IteratorMore(JSContext *cx, JSObject *iterobj, Value *rval)
934 {
935     /* Fast path for native iterators */
936     NativeIterator *ni = NULL;
937     if (iterobj->getClass() == &js_IteratorClass) {
938         /* Key iterators are handled by fast-paths. */
939         ni = iterobj->getNativeIterator();
940         bool more = ni->props_cursor < ni->props_end;
941         if (ni->isKeyIter() || !more) {
942             rval->setBoolean(more);
943             return true;
944         }
945     }
946
947     /* We might still have a pending value. */
948     if (!cx->iterValue.isMagic(JS_NO_ITER_VALUE)) {
949         rval->setBoolean(true);
950         return true;
951     }
952
953     /* Fetch and cache the next value from the iterator. */
954     if (!ni) {
955         jsid id = ATOM_TO_JSID(cx->runtime->atomState.nextAtom);
956         if (!js_GetMethod(cx, iterobj, id, JSGET_METHOD_BARRIER, rval))
957             return false;
958         if (!ExternalInvoke(cx, ObjectValue(*iterobj), *rval, 0, NULL, rval)) {
959             /* Check for StopIteration. */
960             if (!cx->isExceptionPending() || !js_ValueIsStopIteration(cx->getPendingException()))
961                 return false;
962
963             cx->clearPendingException();
964             cx->iterValue.setMagic(JS_NO_ITER_VALUE);
965             rval->setBoolean(false);
966             return true;
967         }
968     } else {
969         JS_ASSERT(!ni->isKeyIter());
970         jsid id = *ni->current();
971         ni->incCursor();
972         if (!ni->obj->getProperty(cx, id, rval))
973             return false;
974         if ((ni->flags & JSITER_KEYVALUE) && !NewKeyValuePair(cx, id, *rval, rval))
975             return false;
976     }
977
978     /* Cache the value returned by iterobj.next() so js_IteratorNext() can find it. */
979     JS_ASSERT(!rval->isMagic(JS_NO_ITER_VALUE));
980     cx->iterValue = *rval;
981     rval->setBoolean(true);
982     return true;
983 }
984
985 JSBool
986 js_IteratorNext(JSContext *cx, JSObject *iterobj, Value *rval)
987 {
988     /* Fast path for native iterators */
989     if (iterobj->getClass() == &js_IteratorClass) {
990         /*
991          * Implement next directly as all the methods of the native iterator are
992          * read-only and permanent.
993          */
994         NativeIterator *ni = iterobj->getNativeIterator();
995         if (ni->isKeyIter()) {
996             JS_ASSERT(ni->props_cursor < ni->props_end);
997             *rval = IdToValue(*ni->current());
998             ni->incCursor();
999
1000             if (rval->isString())
1001                 return true;
1002
1003             JSString *str;
1004             jsint i;
1005             if (rval->isInt32() && (jsuint(i = rval->toInt32()) < INT_STRING_LIMIT)) {
1006                 str = JSString::intString(i);
1007             } else {
1008                 str = js_ValueToString(cx, *rval);
1009                 if (!str)
1010                     return false;
1011             }
1012
1013             rval->setString(str);
1014             return true;
1015         }
1016     }
1017
1018     JS_ASSERT(!cx->iterValue.isMagic(JS_NO_ITER_VALUE));
1019     *rval = cx->iterValue;
1020     cx->iterValue.setMagic(JS_NO_ITER_VALUE);
1021
1022     return true;
1023 }
1024
1025 static JSBool
1026 stopiter_hasInstance(JSContext *cx, JSObject *obj, const Value *v, JSBool *bp)
1027 {
1028     *bp = js_ValueIsStopIteration(*v);
1029     return JS_TRUE;
1030 }
1031
1032 Class js_StopIterationClass = {
1033     js_StopIteration_str,
1034     JSCLASS_HAS_CACHED_PROTO(JSProto_StopIteration) |
1035     JSCLASS_FREEZE_PROTO,
1036     PropertyStub,         /* addProperty */
1037     PropertyStub,         /* delProperty */
1038     PropertyStub,         /* getProperty */
1039     StrictPropertyStub,   /* setProperty */
1040     EnumerateStub,
1041     ResolveStub,
1042     ConvertStub,
1043     NULL,                 /* finalize    */
1044     NULL,                 /* reserved0   */
1045     NULL,                 /* checkAccess */
1046     NULL,                 /* call        */
1047     NULL,                 /* construct   */
1048     NULL,                 /* xdrObject   */
1049     stopiter_hasInstance
1050 };
1051
1052 #if JS_HAS_GENERATORS
1053
1054 static void
1055 generator_finalize(JSContext *cx, JSObject *obj)
1056 {
1057     JSGenerator *gen = (JSGenerator *) obj->getPrivate();
1058     if (!gen)
1059         return;
1060
1061     /*
1062      * gen is open when a script has not called its close method while
1063      * explicitly manipulating it.
1064      */
1065     JS_ASSERT(gen->state == JSGEN_NEWBORN ||
1066               gen->state == JSGEN_CLOSED ||
1067               gen->state == JSGEN_OPEN);
1068     cx->free(gen);
1069 }
1070
1071 static void
1072 generator_trace(JSTracer *trc, JSObject *obj)
1073 {
1074     JSGenerator *gen = (JSGenerator *) obj->getPrivate();
1075     if (!gen)
1076         return;
1077
1078     /*
1079      * Do not mark if the generator is running; the contents may be trash and
1080      * will be replaced when the generator stops.
1081      */
1082     if (gen->state == JSGEN_RUNNING || gen->state == JSGEN_CLOSING)
1083         return;
1084
1085     JSStackFrame *fp = gen->floatingFrame();
1086     JS_ASSERT(gen->liveFrame() == fp);
1087
1088     /*
1089      * Currently, generators are not mjitted. Still, (overflow) args can be
1090      * pushed by the mjit and need to be conservatively marked. Technically, the
1091      * formal args and generator slots are safe for exact marking, but since the
1092      * plan is to eventually mjit generators, it makes sense to future-proof
1093      * this code and save someone an hour later.
1094      */
1095     MarkStackRangeConservatively(trc, gen->floatingStack, fp->formalArgsEnd());
1096     js_TraceStackFrame(trc, fp);
1097     MarkStackRangeConservatively(trc, fp->slots(), gen->regs.sp);
1098 }
1099
1100 Class js_GeneratorClass = {
1101     js_Generator_str,
1102     JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Generator) |
1103     JSCLASS_IS_ANONYMOUS | JSCLASS_MARK_IS_TRACE,
1104     PropertyStub,         /* addProperty */
1105     PropertyStub,         /* delProperty */
1106     PropertyStub,         /* getProperty */
1107     StrictPropertyStub,   /* setProperty */
1108     EnumerateStub,
1109     ResolveStub,
1110     ConvertStub,
1111     generator_finalize,
1112     NULL,                 /* reserved    */
1113     NULL,                 /* checkAccess */
1114     NULL,                 /* call        */
1115     NULL,                 /* construct   */
1116     NULL,                 /* xdrObject   */
1117     NULL,                 /* hasInstance */
1118     JS_CLASS_TRACE(generator_trace),
1119     {
1120         NULL,             /* equality       */
1121         NULL,             /* outerObject    */
1122         NULL,             /* innerObject    */
1123         iterator_iterator,
1124         NULL              /* unused */
1125     }
1126 };
1127
1128 static inline void
1129 RebaseRegsFromTo(JSFrameRegs *regs, JSStackFrame *from, JSStackFrame *to)
1130 {
1131     regs->fp = to;
1132     regs->sp = to->slots() + (regs->sp - from->slots());
1133 }
1134
1135 /*
1136  * Called from the JSOP_GENERATOR case in the interpreter, with fp referring
1137  * to the frame by which the generator function was activated.  Create a new
1138  * JSGenerator object, which contains its own JSStackFrame that we populate
1139  * from *fp.  We know that upon return, the JSOP_GENERATOR opcode will return
1140  * from the activation in fp, so we can steal away fp->callobj and fp->argsobj
1141  * if they are non-null.
1142  */
1143 JS_REQUIRES_STACK JSObject *
1144 js_NewGenerator(JSContext *cx)
1145 {
1146     JSObject *obj = NewBuiltinClassInstance(cx, &js_GeneratorClass);
1147     if (!obj)
1148         return NULL;
1149
1150     JSStackFrame *stackfp = cx->fp();
1151     JS_ASSERT(stackfp->base() == cx->regs->sp);
1152     JS_ASSERT(stackfp->actualArgs() <= stackfp->formalArgs());
1153
1154     /* Load and compute stack slot counts. */
1155     Value *stackvp = stackfp->actualArgs() - 2;
1156     uintN vplen = stackfp->formalArgsEnd() - stackvp;
1157
1158     /* Compute JSGenerator size. */
1159     uintN nbytes = sizeof(JSGenerator) +
1160                    (-1 + /* one Value included in JSGenerator */
1161                     vplen +
1162                     VALUES_PER_STACK_FRAME +
1163                     stackfp->numSlots()) * sizeof(Value);
1164
1165     JSGenerator *gen = (JSGenerator *) cx->malloc(nbytes);
1166     if (!gen)
1167         return NULL;
1168
1169     /* Cut up floatingStack space. */
1170     Value *genvp = gen->floatingStack;
1171     JSStackFrame *genfp = reinterpret_cast<JSStackFrame *>(genvp + vplen);
1172
1173     /* Initialize JSGenerator. */
1174     gen->obj = obj;
1175     gen->state = JSGEN_NEWBORN;
1176     gen->enumerators = NULL;
1177     gen->floating = genfp;
1178
1179     /* Initialize regs stored in generator. */
1180     gen->regs = *cx->regs;
1181     RebaseRegsFromTo(&gen->regs, stackfp, genfp);
1182
1183     /* Copy frame off the stack. */
1184     genfp->stealFrameAndSlots(genvp, stackfp, stackvp, cx->regs->sp);
1185     genfp->initFloatingGenerator();
1186
1187     obj->setPrivate(gen);
1188     return obj;
1189 }
1190
1191 JSGenerator *
1192 js_FloatingFrameToGenerator(JSStackFrame *fp)
1193 {
1194     JS_ASSERT(fp->isGeneratorFrame() && fp->isFloatingGenerator());
1195     char *floatingStackp = (char *)(fp->actualArgs() - 2);
1196     char *p = floatingStackp - offsetof(JSGenerator, floatingStack);
1197     return reinterpret_cast<JSGenerator *>(p);
1198 }
1199
1200 typedef enum JSGeneratorOp {
1201     JSGENOP_NEXT,
1202     JSGENOP_SEND,
1203     JSGENOP_THROW,
1204     JSGENOP_CLOSE
1205 } JSGeneratorOp;
1206
1207 /*
1208  * Start newborn or restart yielding generator and perform the requested
1209  * operation inside its frame.
1210  */
1211 static JS_REQUIRES_STACK JSBool
1212 SendToGenerator(JSContext *cx, JSGeneratorOp op, JSObject *obj,
1213                 JSGenerator *gen, const Value &arg)
1214 {
1215     if (gen->state == JSGEN_RUNNING || gen->state == JSGEN_CLOSING) {
1216         js_ReportValueError(cx, JSMSG_NESTING_GENERATOR,
1217                             JSDVG_SEARCH_STACK, ObjectOrNullValue(obj),
1218                             JS_GetFunctionId(gen->floatingFrame()->fun()));
1219         return JS_FALSE;
1220     }
1221
1222     /* Check for OOM errors here, where we can fail easily. */
1223     if (!cx->ensureGeneratorStackSpace())
1224         return JS_FALSE;
1225
1226     JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_OPEN);
1227     switch (op) {
1228       case JSGENOP_NEXT:
1229       case JSGENOP_SEND:
1230         if (gen->state == JSGEN_OPEN) {
1231             /*
1232              * Store the argument to send as the result of the yield
1233              * expression.
1234              */
1235             gen->regs.sp[-1] = arg;
1236         }
1237         gen->state = JSGEN_RUNNING;
1238         break;
1239
1240       case JSGENOP_THROW:
1241         cx->setPendingException(arg);
1242         gen->state = JSGEN_RUNNING;
1243         break;
1244
1245       default:
1246         JS_ASSERT(op == JSGENOP_CLOSE);
1247         cx->setPendingException(MagicValue(JS_GENERATOR_CLOSING));
1248         gen->state = JSGEN_CLOSING;
1249         break;
1250     }
1251
1252     JSStackFrame *genfp = gen->floatingFrame();
1253     Value *genvp = gen->floatingStack;
1254     uintN vplen = genfp->formalArgsEnd() - genvp;
1255
1256     JSStackFrame *stackfp;
1257     Value *stackvp;
1258     JSBool ok;
1259     {
1260         /*
1261          * Get a pointer to new frame/slots. This memory is not "claimed", so
1262          * the code before pushExecuteFrame must not reenter the interpreter.
1263          */
1264         GeneratorFrameGuard frame;
1265         if (!cx->stack().getGeneratorFrame(cx, vplen, genfp->numSlots(), &frame)) {
1266             gen->state = JSGEN_CLOSED;
1267             return JS_FALSE;
1268         }
1269         stackfp = frame.fp();
1270         stackvp = frame.vp();
1271
1272         /* Copy frame onto the stack. */
1273         stackfp->stealFrameAndSlots(stackvp, genfp, genvp, gen->regs.sp);
1274         stackfp->resetGeneratorPrev(cx);
1275         stackfp->unsetFloatingGenerator();
1276         RebaseRegsFromTo(&gen->regs, genfp, stackfp);
1277         MUST_FLOW_THROUGH("restore");
1278
1279         /* Officially push frame. frame's destructor pops. */
1280         cx->stack().pushGeneratorFrame(cx, &gen->regs, &frame);
1281
1282         cx->enterGenerator(gen);   /* OOM check above. */
1283         JSObject *enumerators = cx->enumerators;
1284         cx->enumerators = gen->enumerators;
1285
1286         ok = RunScript(cx, stackfp->script(), stackfp);
1287
1288         gen->enumerators = cx->enumerators;
1289         cx->enumerators = enumerators;
1290         cx->leaveGenerator(gen);
1291
1292         /*
1293          * Copy the stack frame and rebase the regs, but not before popping
1294          * the stack, since cx->regs == &gen->regs.
1295          */
1296         genfp->stealFrameAndSlots(genvp, stackfp, stackvp, gen->regs.sp);
1297         genfp->setFloatingGenerator();
1298     }
1299     MUST_FLOW_LABEL(restore)
1300     RebaseRegsFromTo(&gen->regs, stackfp, genfp);
1301
1302     if (gen->floatingFrame()->isYielding()) {
1303         /* Yield cannot fail, throw or be called on closing. */
1304         JS_ASSERT(ok);
1305         JS_ASSERT(!cx->isExceptionPending());
1306         JS_ASSERT(gen->state == JSGEN_RUNNING);
1307         JS_ASSERT(op != JSGENOP_CLOSE);
1308         genfp->clearYielding();
1309         gen->state = JSGEN_OPEN;
1310         return JS_TRUE;
1311     }
1312
1313     genfp->clearReturnValue();
1314     gen->state = JSGEN_CLOSED;
1315     if (ok) {
1316         /* Returned, explicitly or by falling off the end. */
1317         if (op == JSGENOP_CLOSE)
1318             return JS_TRUE;
1319         return js_ThrowStopIteration(cx);
1320     }
1321
1322     /*
1323      * An error, silent termination by operation callback or an exception.
1324      * Propagate the condition to the caller.
1325      */
1326     return JS_FALSE;
1327 }
1328
1329 static JS_REQUIRES_STACK JSBool
1330 CloseGenerator(JSContext *cx, JSObject *obj)
1331 {
1332     JS_ASSERT(obj->getClass() == &js_GeneratorClass);
1333
1334     JSGenerator *gen = (JSGenerator *) obj->getPrivate();
1335     if (!gen) {
1336         /* Generator prototype object. */
1337         return JS_TRUE;
1338     }
1339
1340     if (gen->state == JSGEN_CLOSED)
1341         return JS_TRUE;
1342
1343     return SendToGenerator(cx, JSGENOP_CLOSE, obj, gen, UndefinedValue());
1344 }
1345
1346 /*
1347  * Common subroutine of generator_(next|send|throw|close) methods.
1348  */
1349 static JSBool
1350 generator_op(JSContext *cx, JSGeneratorOp op, Value *vp, uintN argc)
1351 {
1352     LeaveTrace(cx);
1353
1354     JSObject *obj = ToObject(cx, &vp[1]);
1355     if (!obj || !InstanceOf(cx, obj, &js_GeneratorClass, vp + 2))
1356         return JS_FALSE;
1357
1358     JSGenerator *gen = (JSGenerator *) obj->getPrivate();
1359     if (!gen) {
1360         /* This happens when obj is the generator prototype. See bug 352885. */
1361         goto closed_generator;
1362     }
1363
1364     if (gen->state == JSGEN_NEWBORN) {
1365         switch (op) {
1366           case JSGENOP_NEXT:
1367           case JSGENOP_THROW:
1368             break;
1369
1370           case JSGENOP_SEND:
1371             if (argc >= 1 && !vp[2].isUndefined()) {
1372                 js_ReportValueError(cx, JSMSG_BAD_GENERATOR_SEND,
1373                                     JSDVG_SEARCH_STACK, vp[2], NULL);
1374                 return JS_FALSE;
1375             }
1376             break;
1377
1378           default:
1379             JS_ASSERT(op == JSGENOP_CLOSE);
1380             gen->state = JSGEN_CLOSED;
1381             return JS_TRUE;
1382         }
1383     } else if (gen->state == JSGEN_CLOSED) {
1384       closed_generator:
1385         switch (op) {
1386           case JSGENOP_NEXT:
1387           case JSGENOP_SEND:
1388             return js_ThrowStopIteration(cx);
1389           case JSGENOP_THROW:
1390             cx->setPendingException(argc >= 1 ? vp[2] : UndefinedValue());
1391             return JS_FALSE;
1392           default:
1393             JS_ASSERT(op == JSGENOP_CLOSE);
1394             return JS_TRUE;
1395         }
1396     }
1397
1398     bool undef = ((op == JSGENOP_SEND || op == JSGENOP_THROW) && argc != 0);
1399     if (!SendToGenerator(cx, op, obj, gen, undef ? vp[2] : UndefinedValue()))
1400         return JS_FALSE;
1401     *vp = gen->floatingFrame()->returnValue();
1402     return JS_TRUE;
1403 }
1404
1405 static JSBool
1406 generator_send(JSContext *cx, uintN argc, Value *vp)
1407 {
1408     return generator_op(cx, JSGENOP_SEND, vp, argc);
1409 }
1410
1411 static JSBool
1412 generator_next(JSContext *cx, uintN argc, Value *vp)
1413 {
1414     return generator_op(cx, JSGENOP_NEXT, vp, argc);
1415 }
1416
1417 static JSBool
1418 generator_throw(JSContext *cx, uintN argc, Value *vp)
1419 {
1420     return generator_op(cx, JSGENOP_THROW, vp, argc);
1421 }
1422
1423 static JSBool
1424 generator_close(JSContext *cx, uintN argc, Value *vp)
1425 {
1426     return generator_op(cx, JSGENOP_CLOSE, vp, argc);
1427 }
1428
1429 static JSFunctionSpec generator_methods[] = {
1430     JS_FN(js_next_str,      generator_next,     0,JSPROP_ROPERM),
1431     JS_FN(js_send_str,      generator_send,     1,JSPROP_ROPERM),
1432     JS_FN(js_throw_str,     generator_throw,    1,JSPROP_ROPERM),
1433     JS_FN(js_close_str,     generator_close,    0,JSPROP_ROPERM),
1434     JS_FS_END
1435 };
1436
1437 #endif /* JS_HAS_GENERATORS */
1438
1439 JSObject *
1440 js_InitIteratorClasses(JSContext *cx, JSObject *obj)
1441 {
1442     JSObject *proto, *stop;
1443
1444     /* Idempotency required: we initialize several things, possibly lazily. */
1445     if (!js_GetClassObject(cx, obj, JSProto_StopIteration, &stop))
1446         return NULL;
1447     if (stop)
1448         return stop;
1449
1450     proto = js_InitClass(cx, obj, NULL, &js_IteratorClass, Iterator, 2,
1451                          NULL, iterator_methods, NULL, NULL);
1452     if (!proto)
1453         return NULL;
1454
1455 #if JS_HAS_GENERATORS
1456     /* Initialize the generator internals if configured. */
1457     if (!js_InitClass(cx, obj, NULL, &js_GeneratorClass, NULL, 0,
1458                       NULL, generator_methods, NULL, NULL)) {
1459         return NULL;
1460     }
1461 #endif
1462
1463     return js_InitClass(cx, obj, NULL, &js_StopIterationClass, NULL, 0,
1464                         NULL, NULL, NULL, NULL);
1465 }