summaryrefslogtreecommitdiff
path: root/js/src/jsiter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jsiter.cpp')
-rw-r--r--js/src/jsiter.cpp1465
1 files changed, 1465 insertions, 0 deletions
diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp
new file mode 100644
index 0000000..8d8a62f
--- /dev/null
+++ b/js/src/jsiter.cpp
@@ -0,0 +1,1465 @@
+/* -*- 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 <string.h> /* for memcpy */
+#include "jstypes.h"
+#include "jsstdint.h"
+#include "jsutil.h"
+#include "jsarena.h"
+#include "jsapi.h"
+#include "jsarray.h"
+#include "jsatom.h"
+#include "jsbool.h"
+#include "jsbuiltins.h"
+#include "jscntxt.h"
+#include "jsversion.h"
+#include "jsexn.h"
+#include "jsfun.h"
+#include "jsgc.h"
+#include "jshashtable.h"
+#include "jsinterp.h"
+#include "jsiter.h"
+#include "jslock.h"
+#include "jsnum.h"
+#include "jsobj.h"
+#include "jsopcode.h"
+#include "jsproxy.h"
+#include "jsscan.h"
+#include "jsscope.h"
+#include "jsscript.h"
+#include "jsstaticcheck.h"
+#include "jstracer.h"
+#include "jsvector.h"
+
+#if JS_HAS_XML_SUPPORT
+#include "jsxml.h"
+#endif
+
+#include "jscntxtinlines.h"
+#include "jsinterpinlines.h"
+#include "jsobjinlines.h"
+#include "jsstrinlines.h"
+
+using namespace js;
+using namespace js::gc;
+
+static void iterator_finalize(JSContext *cx, JSObject *obj);
+static void iterator_trace(JSTracer *trc, JSObject *obj);
+static JSObject *iterator_iterator(JSContext *cx, JSObject *obj, JSBool keysonly);
+
+Class js_IteratorClass = {
+ "Iterator",
+ JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator) |
+ JSCLASS_MARK_IS_TRACE,
+ PropertyStub, /* addProperty */
+ PropertyStub, /* delProperty */
+ PropertyStub, /* getProperty */
+ StrictPropertyStub, /* setProperty */
+ EnumerateStub,
+ ResolveStub,
+ ConvertStub,
+ iterator_finalize,
+ NULL, /* reserved */
+ NULL, /* checkAccess */
+ NULL, /* call */
+ NULL, /* construct */
+ NULL, /* xdrObject */
+ NULL, /* hasInstance */
+ JS_CLASS_TRACE(iterator_trace),
+ {
+ NULL, /* equality */
+ NULL, /* outerObject */
+ NULL, /* innerObject */
+ iterator_iterator,
+ NULL /* unused */
+ }
+};
+
+void
+NativeIterator::mark(JSTracer *trc)
+{
+ MarkIdRange(trc, begin(), end(), "props");
+ if (obj)
+ MarkObject(trc, *obj, "obj");
+}
+
+static void
+iterator_finalize(JSContext *cx, JSObject *obj)
+{
+ JS_ASSERT(obj->getClass() == &js_IteratorClass);
+
+ NativeIterator *ni = obj->getNativeIterator();
+ if (ni) {
+ cx->free(ni);
+ obj->setNativeIterator(NULL);
+ }
+}
+
+static void
+iterator_trace(JSTracer *trc, JSObject *obj)
+{
+ NativeIterator *ni = obj->getNativeIterator();
+
+ if (ni)
+ ni->mark(trc);
+}
+
+struct IdHashPolicy {
+ typedef jsid Lookup;
+ static HashNumber hash(jsid id) {
+ return JSID_BITS(id);
+ }
+ static bool match(jsid id1, jsid id2) {
+ return id1 == id2;
+ }
+};
+
+typedef HashSet<jsid, IdHashPolicy, ContextAllocPolicy> IdSet;
+
+static inline bool
+NewKeyValuePair(JSContext *cx, jsid id, const Value &val, Value *rval)
+{
+ Value vec[2] = { IdToValue(id), val };
+ AutoArrayRooter tvr(cx, JS_ARRAY_LENGTH(vec), vec);
+
+ JSObject *aobj = NewDenseCopiedArray(cx, 2, vec);
+ if (!aobj)
+ return false;
+ rval->setObject(*aobj);
+ return true;
+}
+
+static inline bool
+Enumerate(JSContext *cx, JSObject *obj, JSObject *pobj, jsid id,
+ bool enumerable, bool sharedPermanent, uintN flags, IdSet& ht,
+ AutoIdVector *props)
+{
+ IdSet::AddPtr p = ht.lookupForAdd(id);
+ JS_ASSERT_IF(obj == pobj && !obj->isProxy(), !p);
+
+ /* If we've already seen this, we definitely won't add it. */
+ if (JS_UNLIKELY(!!p))
+ return true;
+
+ /*
+ * It's not necessary to add properties to the hash table at the end of the
+ * prototype chain -- but a proxy might return duplicated properties, so
+ * always add for them.
+ */
+ if ((pobj->getProto() || pobj->isProxy()) && !ht.add(p, id))
+ return false;
+
+ if (JS_UNLIKELY(flags & JSITER_OWNONLY)) {
+ /*
+ * Shared-permanent hack: If this property is shared permanent
+ * and pobj and obj have the same class, then treat it as an own
+ * property of obj, even if pobj != obj. (But see bug 575997.)
+ *
+ * Omit the magic __proto__ property so that JS code can use
+ * Object.getOwnPropertyNames without worrying about it.
+ */
+ if (!pobj->getProto() && id == ATOM_TO_JSID(cx->runtime->atomState.protoAtom))
+ return true;
+ if (pobj != obj && !(sharedPermanent && pobj->getClass() == obj->getClass()))
+ return true;
+ }
+
+ if (enumerable || (flags & JSITER_HIDDEN))
+ return props->append(id);
+
+ return true;
+}
+
+static bool
+EnumerateNativeProperties(JSContext *cx, JSObject *obj, JSObject *pobj, uintN flags, IdSet &ht,
+ AutoIdVector *props)
+{
+ size_t initialLength = props->length();
+
+ /* Collect all unique properties from this object's scope. */
+ for (Shape::Range r = pobj->lastProperty()->all(); !r.empty(); r.popFront()) {
+ const Shape &shape = r.front();
+
+ if (!JSID_IS_DEFAULT_XML_NAMESPACE(shape.id) &&
+ !shape.isAlias() &&
+ !Enumerate(cx, obj, pobj, shape.id, shape.enumerable(),
+ shape.isSharedPermanent(), flags, ht, props))
+ {
+ return false;
+ }
+ }
+
+ Reverse(props->begin() + initialLength, props->end());
+ return true;
+}
+
+static bool
+EnumerateDenseArrayProperties(JSContext *cx, JSObject *obj, JSObject *pobj, uintN flags,
+ IdSet &ht, AutoIdVector *props)
+{
+ if (!Enumerate(cx, obj, pobj, ATOM_TO_JSID(cx->runtime->atomState.lengthAtom), false, true,
+ flags, ht, props)) {
+ return false;
+ }
+
+ if (pobj->getArrayLength() > 0) {
+ size_t capacity = pobj->getDenseArrayCapacity();
+ Value *vp = pobj->getDenseArrayElements();
+ for (size_t i = 0; i < capacity; ++i, ++vp) {
+ if (!vp->isMagic(JS_ARRAY_HOLE)) {
+ /* Dense arrays never get so large that i would not fit into an integer id. */
+ if (!Enumerate(cx, obj, pobj, INT_TO_JSID(i), true, false, flags, ht, props))
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+static bool
+Snapshot(JSContext *cx, JSObject *obj, uintN flags, AutoIdVector *props)
+{
+ /*
+ * FIXME: Bug 575997 - We won't need to initialize this hash table if
+ * (flags & JSITER_OWNONLY) when we eliminate inheritance of
+ * shared-permanent properties as own properties.
+ */
+ IdSet ht(cx);
+ if (!ht.init(32))
+ return NULL;
+
+ JSObject *pobj = obj;
+ do {
+ Class *clasp = pobj->getClass();
+ if (pobj->isNative() &&
+ !pobj->getOps()->enumerate &&
+ !(clasp->flags & JSCLASS_NEW_ENUMERATE)) {
+ if (!clasp->enumerate(cx, pobj))
+ return false;
+ if (!EnumerateNativeProperties(cx, obj, pobj, flags, ht, props))
+ return false;
+ } else if (pobj->isDenseArray()) {
+ if (!EnumerateDenseArrayProperties(cx, obj, pobj, flags, ht, props))
+ return false;
+ } else {
+ if (pobj->isProxy()) {
+ AutoIdVector proxyProps(cx);
+ if (flags & JSITER_OWNONLY) {
+ if (flags & JSITER_HIDDEN) {
+ if (!JSProxy::getOwnPropertyNames(cx, pobj, proxyProps))
+ return false;
+ } else {
+ if (!JSProxy::keys(cx, pobj, proxyProps))
+ return false;
+ }
+ } else {
+ if (!JSProxy::enumerate(cx, pobj, proxyProps))
+ return false;
+ }
+ for (size_t n = 0, len = proxyProps.length(); n < len; n++) {
+ if (!Enumerate(cx, obj, pobj, proxyProps[n], true, false, flags, ht, props))
+ return false;
+ }
+ /* Proxy objects enumerate the prototype on their own, so we are done here. */
+ break;
+ }
+ Value state;
+ JSIterateOp op = (flags & JSITER_HIDDEN) ? JSENUMERATE_INIT_ALL : JSENUMERATE_INIT;
+ if (!pobj->enumerate(cx, op, &state, NULL))
+ return false;
+ if (state.isMagic(JS_NATIVE_ENUMERATE)) {
+ if (!EnumerateNativeProperties(cx, obj, pobj, flags, ht, props))
+ return false;
+ } else {
+ while (true) {
+ jsid id;
+ if (!pobj->enumerate(cx, JSENUMERATE_NEXT, &state, &id))
+ return false;
+ if (state.isNull())
+ break;
+ if (!Enumerate(cx, obj, pobj, id, true, false, flags, ht, props))
+ return false;
+ }
+ }
+ }
+
+ if (JS_UNLIKELY(pobj->isXML()))
+ break;
+ } while ((pobj = pobj->getProto()) != NULL);
+
+ return true;
+}
+
+namespace js {
+
+bool
+VectorToIdArray(JSContext *cx, AutoIdVector &props, JSIdArray **idap)
+{
+ JS_STATIC_ASSERT(sizeof(JSIdArray) > sizeof(jsid));
+ size_t len = props.length();
+ size_t idsz = len * sizeof(jsid);
+ size_t sz = (sizeof(JSIdArray) - sizeof(jsid)) + idsz;
+ JSIdArray *ida = static_cast<JSIdArray *>(cx->malloc(sz));
+ if (!ida)
+ return false;
+
+ ida->length = static_cast<jsint>(len);
+ memcpy(ida->vector, props.begin(), idsz);
+ *idap = ida;
+ return true;
+}
+
+JS_FRIEND_API(bool)
+GetPropertyNames(JSContext *cx, JSObject *obj, uintN flags, AutoIdVector *props)
+{
+ return Snapshot(cx, obj, flags & (JSITER_OWNONLY | JSITER_HIDDEN), props);
+}
+
+}
+
+static inline bool
+GetCustomIterator(JSContext *cx, JSObject *obj, uintN flags, Value *vp)
+{
+ /* Check whether we have a valid __iterator__ method. */
+ JSAtom *atom = cx->runtime->atomState.iteratorAtom;
+ if (!js_GetMethod(cx, obj, ATOM_TO_JSID(atom), JSGET_NO_METHOD_BARRIER, vp))
+ return false;
+
+ /* If there is no custom __iterator__ method, we are done here. */
+ if (!vp->isObject()) {
+ vp->setUndefined();
+ return true;
+ }
+
+ /* Otherwise call it and return that object. */
+ LeaveTrace(cx);
+ Value arg = BooleanValue((flags & JSITER_FOREACH) == 0);
+ if (!ExternalInvoke(cx, ObjectValue(*obj), *vp, 1, &arg, vp))
+ return false;
+ if (vp->isPrimitive()) {
+ /*
+ * We are always coming from js_ValueToIterator, and we are no longer on
+ * trace, so the object we are iterating over is on top of the stack (-1).
+ */
+ JSAutoByteString bytes;
+ if (!js_AtomToPrintableString(cx, atom, &bytes))
+ return false;
+ js_ReportValueError2(cx, JSMSG_BAD_TRAP_RETURN_VALUE,
+ -1, ObjectValue(*obj), NULL, bytes.ptr());
+ return false;
+ }
+ return true;
+}
+
+template <typename T>
+static inline bool
+Compare(T *a, T *b, size_t c)
+{
+ size_t n = (c + size_t(7)) / size_t(8);
+ switch (c % 8) {
+ case 0: do { if (*a++ != *b++) return false;
+ case 7: if (*a++ != *b++) return false;
+ case 6: if (*a++ != *b++) return false;
+ case 5: if (*a++ != *b++) return false;
+ case 4: if (*a++ != *b++) return false;
+ case 3: if (*a++ != *b++) return false;
+ case 2: if (*a++ != *b++) return false;
+ case 1: if (*a++ != *b++) return false;
+ } while (--n > 0);
+ }
+ return true;
+}
+
+static inline JSObject *
+NewIteratorObject(JSContext *cx, uintN flags)
+{
+ if (flags & JSITER_ENUMERATE) {
+ /*
+ * Non-escaping native enumerator objects do not need map, proto, or
+ * parent. However, code in jstracer.cpp and elsewhere may find such a
+ * native enumerator object via the stack and (as for all objects that
+ * are not stillborn, with the exception of "NoSuchMethod" internal
+ * helper objects) expect it to have a non-null map pointer, so we
+ * share an empty Enumerator scope in the runtime.
+ */
+ JSObject *obj = js_NewGCObject(cx, FINALIZE_OBJECT0);
+ if (!obj)
+ return false;
+ obj->init(cx, &js_IteratorClass, NULL, NULL, NULL, false);
+ obj->setMap(cx->compartment->emptyEnumeratorShape);
+ return obj;
+ }
+
+ return NewBuiltinClassInstance(cx, &js_IteratorClass);
+}
+
+NativeIterator *
+NativeIterator::allocateIterator(JSContext *cx, uint32 slength, const AutoIdVector &props)
+{
+ size_t plength = props.length();
+ NativeIterator *ni = (NativeIterator *)
+ cx->malloc(sizeof(NativeIterator) + plength * sizeof(jsid) + slength * sizeof(uint32));
+ if (!ni)
+ return NULL;
+ ni->props_array = ni->props_cursor = (jsid *) (ni + 1);
+ ni->props_end = (jsid *)ni->props_array + plength;
+ if (plength)
+ memcpy(ni->props_array, props.begin(), plength * sizeof(jsid));
+ return ni;
+}
+
+inline void
+NativeIterator::init(JSObject *obj, uintN flags, uint32 slength, uint32 key)
+{
+ this->obj = obj;
+ this->flags = flags;
+ this->shapes_array = (uint32 *) this->props_end;
+ this->shapes_length = slength;
+ this->shapes_key = key;
+}
+
+static inline void
+RegisterEnumerator(JSContext *cx, JSObject *iterobj, NativeIterator *ni)
+{
+ /* Register non-escaping native enumerators (for-in) with the current context. */
+ if (ni->flags & JSITER_ENUMERATE) {
+ ni->next = cx->enumerators;
+ cx->enumerators = iterobj;
+
+ JS_ASSERT(!(ni->flags & JSITER_ACTIVE));
+ ni->flags |= JSITER_ACTIVE;
+ }
+}
+
+static inline bool
+VectorToKeyIterator(JSContext *cx, JSObject *obj, uintN flags, AutoIdVector &keys,
+ uint32 slength, uint32 key, Value *vp)
+{
+ JS_ASSERT(!(flags & JSITER_FOREACH));
+
+ JSObject *iterobj = NewIteratorObject(cx, flags);
+ if (!iterobj)
+ return false;
+
+ NativeIterator *ni = NativeIterator::allocateIterator(cx, slength, keys);
+ if (!ni)
+ return false;
+ ni->init(obj, flags, slength, key);
+
+ if (slength) {
+ /*
+ * Fill in the shape array from scratch. We can't use the array that was
+ * computed for the cache lookup earlier, as constructing iterobj could
+ * have triggered a shape-regenerating GC. Don't bother with regenerating
+ * the shape key; if such a GC *does* occur, we can only get hits through
+ * the one-slot lastNativeIterator cache.
+ */
+ JSObject *pobj = obj;
+ size_t ind = 0;
+ do {
+ ni->shapes_array[ind++] = pobj->shape();
+ pobj = pobj->getProto();
+ } while (pobj);
+ JS_ASSERT(ind == slength);
+ }
+
+ iterobj->setNativeIterator(ni);
+ vp->setObject(*iterobj);
+
+ RegisterEnumerator(cx, iterobj, ni);
+ return true;
+}
+
+namespace js {
+
+bool
+VectorToKeyIterator(JSContext *cx, JSObject *obj, uintN flags, AutoIdVector &props, Value *vp)
+{
+ return VectorToKeyIterator(cx, obj, flags, props, 0, 0, vp);
+}
+
+bool
+VectorToValueIterator(JSContext *cx, JSObject *obj, uintN flags, AutoIdVector &keys,
+ Value *vp)
+{
+ JS_ASSERT(flags & JSITER_FOREACH);
+
+ JSObject *iterobj = NewIteratorObject(cx, flags);
+ if (!iterobj)
+ return false;
+
+ NativeIterator *ni = NativeIterator::allocateIterator(cx, 0, keys);
+ if (!ni)
+ return false;
+ ni->init(obj, flags, 0, 0);
+
+ iterobj->setNativeIterator(ni);
+ vp->setObject(*iterobj);
+
+ RegisterEnumerator(cx, iterobj, ni);
+ return true;
+}
+
+bool
+EnumeratedIdVectorToIterator(JSContext *cx, JSObject *obj, uintN flags, AutoIdVector &props, Value *vp)
+{
+ if (!(flags & JSITER_FOREACH))
+ return VectorToKeyIterator(cx, obj, flags, props, vp);
+
+ return VectorToValueIterator(cx, obj, flags, props, vp);
+}
+
+typedef Vector<uint32, 8> ShapeVector;
+
+static inline void
+UpdateNativeIterator(NativeIterator *ni, JSObject *obj)
+{
+ // Update the object for which the native iterator is associated, so
+ // SuppressDeletedPropertyHelper will recognize the iterator as a match.
+ ni->obj = obj;
+}
+
+bool
+GetIterator(JSContext *cx, JSObject *obj, uintN flags, Value *vp)
+{
+ Vector<uint32, 8> shapes(cx);
+ uint32 key = 0;
+
+ bool keysOnly = (flags == JSITER_ENUMERATE);
+
+ if (obj) {
+ /* Enumerate Iterator.prototype directly. */
+ JSIteratorOp op = obj->getClass()->ext.iteratorObject;
+ if (op && (obj->getClass() != &js_IteratorClass || obj->getNativeIterator())) {
+ JSObject *iterobj = op(cx, obj, !(flags & JSITER_FOREACH));
+ if (!iterobj)
+ return false;
+ vp->setObject(*iterobj);
+ return true;
+ }
+
+ if (keysOnly) {
+ /*
+ * Check to see if this is the same as the most recent object which
+ * was iterated over. We don't explicitly check for shapeless
+ * objects here, as they are not inserted into the cache and
+ * will result in a miss.
+ */
+ JSObject *last = cx->compartment->nativeIterCache.last;
+ JSObject *proto = obj->getProto();
+ if (last) {
+ NativeIterator *lastni = last->getNativeIterator();
+ if (!(lastni->flags & (JSITER_ACTIVE|JSITER_UNREUSABLE)) &&
+ obj->isNative() &&
+ obj->shape() == lastni->shapes_array[0] &&
+ proto && proto->isNative() &&
+ proto->shape() == lastni->shapes_array[1] &&
+ !proto->getProto()) {
+ vp->setObject(*last);
+ UpdateNativeIterator(lastni, obj);
+ RegisterEnumerator(cx, last, lastni);
+ return true;
+ }
+ }
+
+ /*
+ * The iterator object for JSITER_ENUMERATE never escapes, so we
+ * don't care for the proper parent/proto to be set. This also
+ * allows us to re-use a previous iterator object that is not
+ * currently active.
+ */
+ JSObject *pobj = obj;
+ do {
+ if (!pobj->isNative() ||
+ obj->getOps()->enumerate ||
+ pobj->getClass()->enumerate != JS_EnumerateStub) {
+ shapes.clear();
+ goto miss;
+ }
+ uint32 shape = pobj->shape();
+ key = (key + (key << 16)) ^ shape;
+ if (!shapes.append(shape))
+ return false;
+ pobj = pobj->getProto();
+ } while (pobj);
+
+ JSObject *iterobj = cx->compartment->nativeIterCache.get(key);
+ if (iterobj) {
+ NativeIterator *ni = iterobj->getNativeIterator();
+ if (!(ni->flags & (JSITER_ACTIVE|JSITER_UNREUSABLE)) &&
+ ni->shapes_key == key &&
+ ni->shapes_length == shapes.length() &&
+ Compare(ni->shapes_array, shapes.begin(), ni->shapes_length)) {
+ vp->setObject(*iterobj);
+
+ UpdateNativeIterator(ni, obj);
+ RegisterEnumerator(cx, iterobj, ni);
+ if (shapes.length() == 2)
+ cx->compartment->nativeIterCache.last = iterobj;
+ return true;
+ }
+ }
+ }
+
+ miss:
+ if (obj->isProxy())
+ return JSProxy::iterate(cx, obj, flags, vp);
+ if (!GetCustomIterator(cx, obj, flags, vp))
+ return false;
+ if (!vp->isUndefined())
+ return true;
+ }
+
+ /* NB: for (var p in null) succeeds by iterating over no properties. */
+
+ AutoIdVector keys(cx);
+ if (flags & JSITER_FOREACH) {
+ if (JS_LIKELY(obj != NULL) && !Snapshot(cx, obj, flags, &keys))
+ return false;
+ JS_ASSERT(shapes.empty());
+ if (!VectorToValueIterator(cx, obj, flags, keys, vp))
+ return false;
+ } else {
+ if (JS_LIKELY(obj != NULL) && !Snapshot(cx, obj, flags, &keys))
+ return false;
+ if (!VectorToKeyIterator(cx, obj, flags, keys, shapes.length(), key, vp))
+ return false;
+ }
+
+ JSObject *iterobj = &vp->toObject();
+
+ /* Cache the iterator object if possible. */
+ if (shapes.length())
+ cx->compartment->nativeIterCache.set(key, iterobj);
+
+ if (shapes.length() == 2)
+ cx->compartment->nativeIterCache.last = iterobj;
+ return true;
+}
+
+}
+
+static JSObject *
+iterator_iterator(JSContext *cx, JSObject *obj, JSBool keysonly)
+{
+ return obj;
+}
+
+static JSBool
+Iterator(JSContext *cx, uintN argc, Value *vp)
+{
+ Value *argv = JS_ARGV(cx, vp);
+ bool keyonly = argc >= 2 ? js_ValueToBoolean(argv[1]) : false;
+ uintN flags = JSITER_OWNONLY | (keyonly ? 0 : (JSITER_FOREACH | JSITER_KEYVALUE));
+ *vp = argc >= 1 ? argv[0] : UndefinedValue();
+ return js_ValueToIterator(cx, flags, vp);
+}
+
+JSBool
+js_ThrowStopIteration(JSContext *cx)
+{
+ Value v;
+
+ JS_ASSERT(!JS_IsExceptionPending(cx));
+ if (js_FindClassObject(cx, NULL, JSProto_StopIteration, &v))
+ cx->setPendingException(v);
+ return JS_FALSE;
+}
+
+static JSBool
+iterator_next(JSContext *cx, uintN argc, Value *vp)
+{
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj || !InstanceOf(cx, obj, &js_IteratorClass, vp + 2))
+ return false;
+
+ if (!js_IteratorMore(cx, obj, vp))
+ return false;
+ if (!vp->toBoolean()) {
+ js_ThrowStopIteration(cx);
+ return false;
+ }
+ return js_IteratorNext(cx, obj, vp);
+}
+
+#define JSPROP_ROPERM (JSPROP_READONLY | JSPROP_PERMANENT)
+
+static JSFunctionSpec iterator_methods[] = {
+ JS_FN(js_next_str, iterator_next, 0,JSPROP_ROPERM),
+ JS_FS_END
+};
+
+/*
+ * Call ToObject(v).__iterator__(keyonly) if ToObject(v).__iterator__ exists.
+ * Otherwise construct the default iterator.
+ */
+JS_FRIEND_API(JSBool)
+js_ValueToIterator(JSContext *cx, uintN flags, Value *vp)
+{
+ /* JSITER_KEYVALUE must always come with JSITER_FOREACH */
+ JS_ASSERT_IF(flags & JSITER_KEYVALUE, flags & JSITER_FOREACH);
+
+ /*
+ * Make sure the more/next state machine doesn't get stuck. A value might be
+ * left in iterValue when a trace is left due to an operation time-out after
+ * JSOP_MOREITER but before the value is picked up by FOR*.
+ */
+ cx->iterValue.setMagic(JS_NO_ITER_VALUE);
+
+ JSObject *obj;
+ if (vp->isObject()) {
+ /* Common case. */
+ obj = &vp->toObject();
+ } else {
+ /*
+ * Enumerating over null and undefined gives an empty enumerator.
+ * This is contrary to ECMA-262 9.9 ToObject, invoked from step 3 of
+ * the first production in 12.6.4 and step 4 of the second production,
+ * but it's "web JS" compatible. ES5 fixed for-in to match this de-facto
+ * standard.
+ */
+ if ((flags & JSITER_ENUMERATE)) {
+ if (!js_ValueToObjectOrNull(cx, *vp, &obj))
+ return false;
+ /* fall through */
+ } else {
+ obj = js_ValueToNonNullObject(cx, *vp);
+ if (!obj)
+ return false;
+ }
+ }
+
+ return GetIterator(cx, obj, flags, vp);
+}
+
+#if JS_HAS_GENERATORS
+static JS_REQUIRES_STACK JSBool
+CloseGenerator(JSContext *cx, JSObject *genobj);
+#endif
+
+JS_FRIEND_API(JSBool)
+js_CloseIterator(JSContext *cx, JSObject *obj)
+{
+ cx->iterValue.setMagic(JS_NO_ITER_VALUE);
+
+ Class *clasp = obj->getClass();
+ if (clasp == &js_IteratorClass) {
+ /* Remove enumerators from the active list, which is a stack. */
+ NativeIterator *ni = obj->getNativeIterator();
+
+ if (ni->flags & JSITER_ENUMERATE) {
+ JS_ASSERT(cx->enumerators == obj);
+ cx->enumerators = ni->next;
+
+ JS_ASSERT(ni->flags & JSITER_ACTIVE);
+ ni->flags &= ~JSITER_ACTIVE;
+
+ /*
+ * Reset the enumerator; it may still be in the cached iterators
+ * for this thread, and can be reused.
+ */
+ ni->props_cursor = ni->props_array;
+ }
+ }
+#if JS_HAS_GENERATORS
+ else if (clasp == &js_GeneratorClass) {
+ return CloseGenerator(cx, obj);
+ }
+#endif
+ return JS_TRUE;
+}
+
+/*
+ * Suppress enumeration of deleted properties. This function must be called
+ * when a property is deleted and there might be active enumerators.
+ *
+ * We maintain a list of active non-escaping for-in enumerators. To suppress
+ * a property, we check whether each active enumerator contains the (obj, id)
+ * pair and has not yet enumerated |id|. If so, and |id| is the next property,
+ * we simply advance the cursor. Otherwise, we delete |id| from the list.
+ *
+ * We do not suppress enumeration of a property deleted along an object's
+ * prototype chain. Only direct deletions on the object are handled.
+ *
+ * This function can suppress multiple properties at once. The |predicate|
+ * argument is an object which can be called on an id and returns true or
+ * false. It also must have a method |matchesAtMostOne| which allows us to
+ * stop searching after the first deletion if true.
+ */
+template<typename IdPredicate>
+static bool
+SuppressDeletedPropertyHelper(JSContext *cx, JSObject *obj, IdPredicate predicate)
+{
+ JSObject *iterobj = cx->enumerators;
+ while (iterobj) {
+ again:
+ NativeIterator *ni = iterobj->getNativeIterator();
+ /* This only works for identified surpressed keys, not values. */
+ if (ni->isKeyIter() && ni->obj == obj && ni->props_cursor < ni->props_end) {
+ /* Check whether id is still to come. */
+ jsid *props_cursor = ni->current();
+ jsid *props_end = ni->end();
+ for (jsid *idp = props_cursor; idp < props_end; ++idp) {
+ if (predicate(*idp)) {
+ /*
+ * Check whether another property along the prototype chain
+ * became visible as a result of this deletion.
+ */
+ if (obj->getProto()) {
+ AutoObjectRooter proto(cx, obj->getProto());
+ AutoObjectRooter obj2(cx);
+ JSProperty *prop;
+ if (!proto.object()->lookupProperty(cx, *idp, obj2.addr(), &prop))
+ return false;
+ if (prop) {
+ uintN attrs;
+ if (obj2.object()->isNative())
+ attrs = ((Shape *) prop)->attributes();
+ else if (!obj2.object()->getAttributes(cx, *idp, &attrs))
+ return false;
+
+ if (attrs & JSPROP_ENUMERATE)
+ continue;
+ }
+ }
+
+ /*
+ * If lookupProperty or getAttributes above removed a property from
+ * ni, start over.
+ */
+ if (props_end != ni->props_end || props_cursor != ni->props_cursor)
+ goto again;
+
+ /*
+ * No property along the prototype chain stepped in to take the
+ * property's place, so go ahead and delete id from the list.
+ * If it is the next property to be enumerated, just skip it.
+ */
+ if (idp == props_cursor) {
+ ni->incCursor();
+ } else {
+ memmove(idp, idp + 1, (props_end - (idp + 1)) * sizeof(jsid));
+ ni->props_end = ni->end() - 1;
+ }
+
+ /* Don't reuse modified native iterators. */
+ ni->flags |= JSITER_UNREUSABLE;
+
+ if (predicate.matchesAtMostOne())
+ break;
+ }
+ }
+ }
+ iterobj = ni->next;
+ }
+ return true;
+}
+
+class SingleIdPredicate {
+ jsid id;
+public:
+ SingleIdPredicate(jsid id) : id(id) {}
+
+ bool operator()(jsid id) { return id == this->id; }
+ bool matchesAtMostOne() { return true; }
+};
+
+bool
+js_SuppressDeletedProperty(JSContext *cx, JSObject *obj, jsid id)
+{
+ id = js_CheckForStringIndex(id);
+ return SuppressDeletedPropertyHelper(cx, obj, SingleIdPredicate(id));
+}
+
+class IndexRangePredicate {
+ jsint begin, end;
+public:
+ IndexRangePredicate(jsint begin, jsint end) : begin(begin), end(end) {}
+
+ bool operator()(jsid id) {
+ return JSID_IS_INT(id) && begin <= JSID_TO_INT(id) && JSID_TO_INT(id) < end;
+ }
+ bool matchesAtMostOne() { return false; }
+};
+
+bool
+js_SuppressDeletedIndexProperties(JSContext *cx, JSObject *obj, jsint begin, jsint end)
+{
+ return SuppressDeletedPropertyHelper(cx, obj, IndexRangePredicate(begin, end));
+}
+
+JSBool
+js_IteratorMore(JSContext *cx, JSObject *iterobj, Value *rval)
+{
+ /* Fast path for native iterators */
+ NativeIterator *ni = NULL;
+ if (iterobj->getClass() == &js_IteratorClass) {
+ /* Key iterators are handled by fast-paths. */
+ ni = iterobj->getNativeIterator();
+ bool more = ni->props_cursor < ni->props_end;
+ if (ni->isKeyIter() || !more) {
+ rval->setBoolean(more);
+ return true;
+ }
+ }
+
+ /* We might still have a pending value. */
+ if (!cx->iterValue.isMagic(JS_NO_ITER_VALUE)) {
+ rval->setBoolean(true);
+ return true;
+ }
+
+ /* Fetch and cache the next value from the iterator. */
+ if (!ni) {
+ jsid id = ATOM_TO_JSID(cx->runtime->atomState.nextAtom);
+ if (!js_GetMethod(cx, iterobj, id, JSGET_METHOD_BARRIER, rval))
+ return false;
+ if (!ExternalInvoke(cx, ObjectValue(*iterobj), *rval, 0, NULL, rval)) {
+ /* Check for StopIteration. */
+ if (!cx->isExceptionPending() || !js_ValueIsStopIteration(cx->getPendingException()))
+ return false;
+
+ cx->clearPendingException();
+ cx->iterValue.setMagic(JS_NO_ITER_VALUE);
+ rval->setBoolean(false);
+ return true;
+ }
+ } else {
+ JS_ASSERT(!ni->isKeyIter());
+ jsid id = *ni->current();
+ ni->incCursor();
+ if (!ni->obj->getProperty(cx, id, rval))
+ return false;
+ if ((ni->flags & JSITER_KEYVALUE) && !NewKeyValuePair(cx, id, *rval, rval))
+ return false;
+ }
+
+ /* Cache the value returned by iterobj.next() so js_IteratorNext() can find it. */
+ JS_ASSERT(!rval->isMagic(JS_NO_ITER_VALUE));
+ cx->iterValue = *rval;
+ rval->setBoolean(true);
+ return true;
+}
+
+JSBool
+js_IteratorNext(JSContext *cx, JSObject *iterobj, Value *rval)
+{
+ /* Fast path for native iterators */
+ if (iterobj->getClass() == &js_IteratorClass) {
+ /*
+ * Implement next directly as all the methods of the native iterator are
+ * read-only and permanent.
+ */
+ NativeIterator *ni = iterobj->getNativeIterator();
+ if (ni->isKeyIter()) {
+ JS_ASSERT(ni->props_cursor < ni->props_end);
+ *rval = IdToValue(*ni->current());
+ ni->incCursor();
+
+ if (rval->isString())
+ return true;
+
+ JSString *str;
+ jsint i;
+ if (rval->isInt32() && (jsuint(i = rval->toInt32()) < INT_STRING_LIMIT)) {
+ str = JSString::intString(i);
+ } else {
+ str = js_ValueToString(cx, *rval);
+ if (!str)
+ return false;
+ }
+
+ rval->setString(str);
+ return true;
+ }
+ }
+
+ JS_ASSERT(!cx->iterValue.isMagic(JS_NO_ITER_VALUE));
+ *rval = cx->iterValue;
+ cx->iterValue.setMagic(JS_NO_ITER_VALUE);
+
+ return true;
+}
+
+static JSBool
+stopiter_hasInstance(JSContext *cx, JSObject *obj, const Value *v, JSBool *bp)
+{
+ *bp = js_ValueIsStopIteration(*v);
+ return JS_TRUE;
+}
+
+Class js_StopIterationClass = {
+ js_StopIteration_str,
+ JSCLASS_HAS_CACHED_PROTO(JSProto_StopIteration) |
+ JSCLASS_FREEZE_PROTO,
+ PropertyStub, /* addProperty */
+ PropertyStub, /* delProperty */
+ PropertyStub, /* getProperty */
+ StrictPropertyStub, /* setProperty */
+ EnumerateStub,
+ ResolveStub,
+ ConvertStub,
+ NULL, /* finalize */
+ NULL, /* reserved0 */
+ NULL, /* checkAccess */
+ NULL, /* call */
+ NULL, /* construct */
+ NULL, /* xdrObject */
+ stopiter_hasInstance
+};
+
+#if JS_HAS_GENERATORS
+
+static void
+generator_finalize(JSContext *cx, JSObject *obj)
+{
+ JSGenerator *gen = (JSGenerator *) obj->getPrivate();
+ if (!gen)
+ return;
+
+ /*
+ * gen is open when a script has not called its close method while
+ * explicitly manipulating it.
+ */
+ JS_ASSERT(gen->state == JSGEN_NEWBORN ||
+ gen->state == JSGEN_CLOSED ||
+ gen->state == JSGEN_OPEN);
+ cx->free(gen);
+}
+
+static void
+generator_trace(JSTracer *trc, JSObject *obj)
+{
+ JSGenerator *gen = (JSGenerator *) obj->getPrivate();
+ if (!gen)
+ return;
+
+ /*
+ * Do not mark if the generator is running; the contents may be trash and
+ * will be replaced when the generator stops.
+ */
+ if (gen->state == JSGEN_RUNNING || gen->state == JSGEN_CLOSING)
+ return;
+
+ JSStackFrame *fp = gen->floatingFrame();
+ JS_ASSERT(gen->liveFrame() == fp);
+
+ /*
+ * Currently, generators are not mjitted. Still, (overflow) args can be
+ * pushed by the mjit and need to be conservatively marked. Technically, the
+ * formal args and generator slots are safe for exact marking, but since the
+ * plan is to eventually mjit generators, it makes sense to future-proof
+ * this code and save someone an hour later.
+ */
+ MarkStackRangeConservatively(trc, gen->floatingStack, fp->formalArgsEnd());
+ js_TraceStackFrame(trc, fp);
+ MarkStackRangeConservatively(trc, fp->slots(), gen->regs.sp);
+}
+
+Class js_GeneratorClass = {
+ js_Generator_str,
+ JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Generator) |
+ JSCLASS_IS_ANONYMOUS | JSCLASS_MARK_IS_TRACE,
+ PropertyStub, /* addProperty */
+ PropertyStub, /* delProperty */
+ PropertyStub, /* getProperty */
+ StrictPropertyStub, /* setProperty */
+ EnumerateStub,
+ ResolveStub,
+ ConvertStub,
+ generator_finalize,
+ NULL, /* reserved */
+ NULL, /* checkAccess */
+ NULL, /* call */
+ NULL, /* construct */
+ NULL, /* xdrObject */
+ NULL, /* hasInstance */
+ JS_CLASS_TRACE(generator_trace),
+ {
+ NULL, /* equality */
+ NULL, /* outerObject */
+ NULL, /* innerObject */
+ iterator_iterator,
+ NULL /* unused */
+ }
+};
+
+static inline void
+RebaseRegsFromTo(JSFrameRegs *regs, JSStackFrame *from, JSStackFrame *to)
+{
+ regs->fp = to;
+ regs->sp = to->slots() + (regs->sp - from->slots());
+}
+
+/*
+ * Called from the JSOP_GENERATOR case in the interpreter, with fp referring
+ * to the frame by which the generator function was activated. Create a new
+ * JSGenerator object, which contains its own JSStackFrame that we populate
+ * from *fp. We know that upon return, the JSOP_GENERATOR opcode will return
+ * from the activation in fp, so we can steal away fp->callobj and fp->argsobj
+ * if they are non-null.
+ */
+JS_REQUIRES_STACK JSObject *
+js_NewGenerator(JSContext *cx)
+{
+ JSObject *obj = NewBuiltinClassInstance(cx, &js_GeneratorClass);
+ if (!obj)
+ return NULL;
+
+ JSStackFrame *stackfp = cx->fp();
+ JS_ASSERT(stackfp->base() == cx->regs->sp);
+ JS_ASSERT(stackfp->actualArgs() <= stackfp->formalArgs());
+
+ /* Load and compute stack slot counts. */
+ Value *stackvp = stackfp->actualArgs() - 2;
+ uintN vplen = stackfp->formalArgsEnd() - stackvp;
+
+ /* Compute JSGenerator size. */
+ uintN nbytes = sizeof(JSGenerator) +
+ (-1 + /* one Value included in JSGenerator */
+ vplen +
+ VALUES_PER_STACK_FRAME +
+ stackfp->numSlots()) * sizeof(Value);
+
+ JSGenerator *gen = (JSGenerator *) cx->malloc(nbytes);
+ if (!gen)
+ return NULL;
+
+ /* Cut up floatingStack space. */
+ Value *genvp = gen->floatingStack;
+ JSStackFrame *genfp = reinterpret_cast<JSStackFrame *>(genvp + vplen);
+
+ /* Initialize JSGenerator. */
+ gen->obj = obj;
+ gen->state = JSGEN_NEWBORN;
+ gen->enumerators = NULL;
+ gen->floating = genfp;
+
+ /* Initialize regs stored in generator. */
+ gen->regs = *cx->regs;
+ RebaseRegsFromTo(&gen->regs, stackfp, genfp);
+
+ /* Copy frame off the stack. */
+ genfp->stealFrameAndSlots(genvp, stackfp, stackvp, cx->regs->sp);
+ genfp->initFloatingGenerator();
+
+ obj->setPrivate(gen);
+ return obj;
+}
+
+JSGenerator *
+js_FloatingFrameToGenerator(JSStackFrame *fp)
+{
+ JS_ASSERT(fp->isGeneratorFrame() && fp->isFloatingGenerator());
+ char *floatingStackp = (char *)(fp->actualArgs() - 2);
+ char *p = floatingStackp - offsetof(JSGenerator, floatingStack);
+ return reinterpret_cast<JSGenerator *>(p);
+}
+
+typedef enum JSGeneratorOp {
+ JSGENOP_NEXT,
+ JSGENOP_SEND,
+ JSGENOP_THROW,
+ JSGENOP_CLOSE
+} JSGeneratorOp;
+
+/*
+ * Start newborn or restart yielding generator and perform the requested
+ * operation inside its frame.
+ */
+static JS_REQUIRES_STACK JSBool
+SendToGenerator(JSContext *cx, JSGeneratorOp op, JSObject *obj,
+ JSGenerator *gen, const Value &arg)
+{
+ if (gen->state == JSGEN_RUNNING || gen->state == JSGEN_CLOSING) {
+ js_ReportValueError(cx, JSMSG_NESTING_GENERATOR,
+ JSDVG_SEARCH_STACK, ObjectOrNullValue(obj),
+ JS_GetFunctionId(gen->floatingFrame()->fun()));
+ return JS_FALSE;
+ }
+
+ /* Check for OOM errors here, where we can fail easily. */
+ if (!cx->ensureGeneratorStackSpace())
+ return JS_FALSE;
+
+ JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_OPEN);
+ switch (op) {
+ case JSGENOP_NEXT:
+ case JSGENOP_SEND:
+ if (gen->state == JSGEN_OPEN) {
+ /*
+ * Store the argument to send as the result of the yield
+ * expression.
+ */
+ gen->regs.sp[-1] = arg;
+ }
+ gen->state = JSGEN_RUNNING;
+ break;
+
+ case JSGENOP_THROW:
+ cx->setPendingException(arg);
+ gen->state = JSGEN_RUNNING;
+ break;
+
+ default:
+ JS_ASSERT(op == JSGENOP_CLOSE);
+ cx->setPendingException(MagicValue(JS_GENERATOR_CLOSING));
+ gen->state = JSGEN_CLOSING;
+ break;
+ }
+
+ JSStackFrame *genfp = gen->floatingFrame();
+ Value *genvp = gen->floatingStack;
+ uintN vplen = genfp->formalArgsEnd() - genvp;
+
+ JSStackFrame *stackfp;
+ Value *stackvp;
+ JSBool ok;
+ {
+ /*
+ * Get a pointer to new frame/slots. This memory is not "claimed", so
+ * the code before pushExecuteFrame must not reenter the interpreter.
+ */
+ GeneratorFrameGuard frame;
+ if (!cx->stack().getGeneratorFrame(cx, vplen, genfp->numSlots(), &frame)) {
+ gen->state = JSGEN_CLOSED;
+ return JS_FALSE;
+ }
+ stackfp = frame.fp();
+ stackvp = frame.vp();
+
+ /* Copy frame onto the stack. */
+ stackfp->stealFrameAndSlots(stackvp, genfp, genvp, gen->regs.sp);
+ stackfp->resetGeneratorPrev(cx);
+ stackfp->unsetFloatingGenerator();
+ RebaseRegsFromTo(&gen->regs, genfp, stackfp);
+ MUST_FLOW_THROUGH("restore");
+
+ /* Officially push frame. frame's destructor pops. */
+ cx->stack().pushGeneratorFrame(cx, &gen->regs, &frame);
+
+ cx->enterGenerator(gen); /* OOM check above. */
+ JSObject *enumerators = cx->enumerators;
+ cx->enumerators = gen->enumerators;
+
+ ok = RunScript(cx, stackfp->script(), stackfp);
+
+ gen->enumerators = cx->enumerators;
+ cx->enumerators = enumerators;
+ cx->leaveGenerator(gen);
+
+ /*
+ * Copy the stack frame and rebase the regs, but not before popping
+ * the stack, since cx->regs == &gen->regs.
+ */
+ genfp->stealFrameAndSlots(genvp, stackfp, stackvp, gen->regs.sp);
+ genfp->setFloatingGenerator();
+ }
+ MUST_FLOW_LABEL(restore)
+ RebaseRegsFromTo(&gen->regs, stackfp, genfp);
+
+ if (gen->floatingFrame()->isYielding()) {
+ /* Yield cannot fail, throw or be called on closing. */
+ JS_ASSERT(ok);
+ JS_ASSERT(!cx->isExceptionPending());
+ JS_ASSERT(gen->state == JSGEN_RUNNING);
+ JS_ASSERT(op != JSGENOP_CLOSE);
+ genfp->clearYielding();
+ gen->state = JSGEN_OPEN;
+ return JS_TRUE;
+ }
+
+ genfp->clearReturnValue();
+ gen->state = JSGEN_CLOSED;
+ if (ok) {
+ /* Returned, explicitly or by falling off the end. */
+ if (op == JSGENOP_CLOSE)
+ return JS_TRUE;
+ return js_ThrowStopIteration(cx);
+ }
+
+ /*
+ * An error, silent termination by operation callback or an exception.
+ * Propagate the condition to the caller.
+ */
+ return JS_FALSE;
+}
+
+static JS_REQUIRES_STACK JSBool
+CloseGenerator(JSContext *cx, JSObject *obj)
+{
+ JS_ASSERT(obj->getClass() == &js_GeneratorClass);
+
+ JSGenerator *gen = (JSGenerator *) obj->getPrivate();
+ if (!gen) {
+ /* Generator prototype object. */
+ return JS_TRUE;
+ }
+
+ if (gen->state == JSGEN_CLOSED)
+ return JS_TRUE;
+
+ return SendToGenerator(cx, JSGENOP_CLOSE, obj, gen, UndefinedValue());
+}
+
+/*
+ * Common subroutine of generator_(next|send|throw|close) methods.
+ */
+static JSBool
+generator_op(JSContext *cx, JSGeneratorOp op, Value *vp, uintN argc)
+{
+ LeaveTrace(cx);
+
+ JSObject *obj = ToObject(cx, &vp[1]);
+ if (!obj || !InstanceOf(cx, obj, &js_GeneratorClass, vp + 2))
+ return JS_FALSE;
+
+ JSGenerator *gen = (JSGenerator *) obj->getPrivate();
+ if (!gen) {
+ /* This happens when obj is the generator prototype. See bug 352885. */
+ goto closed_generator;
+ }
+
+ if (gen->state == JSGEN_NEWBORN) {
+ switch (op) {
+ case JSGENOP_NEXT:
+ case JSGENOP_THROW:
+ break;
+
+ case JSGENOP_SEND:
+ if (argc >= 1 && !vp[2].isUndefined()) {
+ js_ReportValueError(cx, JSMSG_BAD_GENERATOR_SEND,
+ JSDVG_SEARCH_STACK, vp[2], NULL);
+ return JS_FALSE;
+ }
+ break;
+
+ default:
+ JS_ASSERT(op == JSGENOP_CLOSE);
+ gen->state = JSGEN_CLOSED;
+ return JS_TRUE;
+ }
+ } else if (gen->state == JSGEN_CLOSED) {
+ closed_generator:
+ switch (op) {
+ case JSGENOP_NEXT:
+ case JSGENOP_SEND:
+ return js_ThrowStopIteration(cx);
+ case JSGENOP_THROW:
+ cx->setPendingException(argc >= 1 ? vp[2] : UndefinedValue());
+ return JS_FALSE;
+ default:
+ JS_ASSERT(op == JSGENOP_CLOSE);
+ return JS_TRUE;
+ }
+ }
+
+ bool undef = ((op == JSGENOP_SEND || op == JSGENOP_THROW) && argc != 0);
+ if (!SendToGenerator(cx, op, obj, gen, undef ? vp[2] : UndefinedValue()))
+ return JS_FALSE;
+ *vp = gen->floatingFrame()->returnValue();
+ return JS_TRUE;
+}
+
+static JSBool
+generator_send(JSContext *cx, uintN argc, Value *vp)
+{
+ return generator_op(cx, JSGENOP_SEND, vp, argc);
+}
+
+static JSBool
+generator_next(JSContext *cx, uintN argc, Value *vp)
+{
+ return generator_op(cx, JSGENOP_NEXT, vp, argc);
+}
+
+static JSBool
+generator_throw(JSContext *cx, uintN argc, Value *vp)
+{
+ return generator_op(cx, JSGENOP_THROW, vp, argc);
+}
+
+static JSBool
+generator_close(JSContext *cx, uintN argc, Value *vp)
+{
+ return generator_op(cx, JSGENOP_CLOSE, vp, argc);
+}
+
+static JSFunctionSpec generator_methods[] = {
+ JS_FN(js_next_str, generator_next, 0,JSPROP_ROPERM),
+ JS_FN(js_send_str, generator_send, 1,JSPROP_ROPERM),
+ JS_FN(js_throw_str, generator_throw, 1,JSPROP_ROPERM),
+ JS_FN(js_close_str, generator_close, 0,JSPROP_ROPERM),
+ JS_FS_END
+};
+
+#endif /* JS_HAS_GENERATORS */
+
+JSObject *
+js_InitIteratorClasses(JSContext *cx, JSObject *obj)
+{
+ JSObject *proto, *stop;
+
+ /* Idempotency required: we initialize several things, possibly lazily. */
+ if (!js_GetClassObject(cx, obj, JSProto_StopIteration, &stop))
+ return NULL;
+ if (stop)
+ return stop;
+
+ proto = js_InitClass(cx, obj, NULL, &js_IteratorClass, Iterator, 2,
+ NULL, iterator_methods, NULL, NULL);
+ if (!proto)
+ return NULL;
+
+#if JS_HAS_GENERATORS
+ /* Initialize the generator internals if configured. */
+ if (!js_InitClass(cx, obj, NULL, &js_GeneratorClass, NULL, 0,
+ NULL, generator_methods, NULL, NULL)) {
+ return NULL;
+ }
+#endif
+
+ return js_InitClass(cx, obj, NULL, &js_StopIterationClass, NULL, 0,
+ NULL, NULL, NULL, NULL);
+}