michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* JavaScript iterators. */ michael@0: michael@0: #include "jsiter.h" michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/PodOperations.h" michael@0: michael@0: #include "jsarray.h" michael@0: #include "jsatom.h" michael@0: #include "jscntxt.h" michael@0: #include "jsgc.h" michael@0: #include "jsobj.h" michael@0: #include "jsopcode.h" michael@0: #include "jsproxy.h" michael@0: #include "jsscript.h" michael@0: #include "jstypes.h" michael@0: #include "jsutil.h" michael@0: michael@0: #include "ds/Sort.h" michael@0: #include "gc/Marking.h" michael@0: #include "vm/GeneratorObject.h" michael@0: #include "vm/GlobalObject.h" michael@0: #include "vm/Interpreter.h" michael@0: #include "vm/Shape.h" michael@0: #include "vm/StopIterationObject.h" michael@0: michael@0: #include "jsinferinlines.h" michael@0: #include "jsobjinlines.h" michael@0: #include "jsscriptinlines.h" michael@0: michael@0: #include "vm/Stack-inl.h" michael@0: #include "vm/String-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: using JS::ForOfIterator; michael@0: michael@0: using mozilla::ArrayLength; michael@0: #ifdef JS_MORE_DETERMINISTIC michael@0: using mozilla::PodCopy; michael@0: #endif michael@0: using mozilla::PodZero; michael@0: michael@0: typedef Rooted RootedPropertyIteratorObject; michael@0: michael@0: static const gc::AllocKind ITERATOR_FINALIZE_KIND = gc::FINALIZE_OBJECT2_BACKGROUND; michael@0: michael@0: void michael@0: NativeIterator::mark(JSTracer *trc) michael@0: { michael@0: for (HeapPtr *str = begin(); str < end(); str++) michael@0: MarkString(trc, str, "prop"); michael@0: if (obj) michael@0: MarkObject(trc, &obj, "obj"); michael@0: michael@0: // The SuppressDeletedPropertyHelper loop can GC, so make sure that if the michael@0: // GC removes any elements from the list, it won't remove this one. michael@0: if (iterObj_) michael@0: MarkObjectUnbarriered(trc, &iterObj_, "iterObj"); michael@0: } michael@0: michael@0: struct IdHashPolicy { michael@0: typedef jsid Lookup; michael@0: static HashNumber hash(jsid id) { michael@0: return JSID_BITS(id); michael@0: } michael@0: static bool match(jsid id1, jsid id2) { michael@0: return id1 == id2; michael@0: } michael@0: }; michael@0: michael@0: typedef HashSet IdSet; michael@0: michael@0: static inline bool michael@0: NewKeyValuePair(JSContext *cx, jsid id, const Value &val, MutableHandleValue rval) michael@0: { michael@0: JS::AutoValueArray<2> vec(cx); michael@0: vec[0].set(IdToValue(id)); michael@0: vec[1].set(val); michael@0: michael@0: JSObject *aobj = NewDenseCopiedArray(cx, 2, vec.begin()); michael@0: if (!aobj) michael@0: return false; michael@0: rval.setObject(*aobj); michael@0: return true; michael@0: } michael@0: michael@0: static inline bool michael@0: Enumerate(JSContext *cx, HandleObject pobj, jsid id, michael@0: bool enumerable, unsigned flags, IdSet& ht, AutoIdVector *props) michael@0: { michael@0: /* michael@0: * We implement __proto__ using a property on |Object.prototype|, but michael@0: * because __proto__ is highly deserving of removal, we don't want it to michael@0: * show up in property enumeration, even if only for |Object.prototype| michael@0: * (think introspection by Prototype-like frameworks that add methods to michael@0: * the built-in prototypes). So exclude __proto__ if the object where the michael@0: * property was found has no [[Prototype]] and might be |Object.prototype|. michael@0: */ michael@0: if (MOZ_UNLIKELY(!pobj->getTaggedProto().isObject() && JSID_IS_ATOM(id, cx->names().proto))) michael@0: return true; michael@0: michael@0: if (!(flags & JSITER_OWNONLY) || pobj->is() || pobj->getOps()->enumerate) { michael@0: /* If we've already seen this, we definitely won't add it. */ michael@0: IdSet::AddPtr p = ht.lookupForAdd(id); michael@0: if (MOZ_UNLIKELY(!!p)) michael@0: return true; michael@0: michael@0: /* michael@0: * It's not necessary to add properties to the hash table at the end of michael@0: * the prototype chain, but custom enumeration behaviors might return michael@0: * duplicated properties, so always add in such cases. michael@0: */ michael@0: if ((pobj->is() || pobj->getProto() || pobj->getOps()->enumerate) && !ht.add(p, id)) michael@0: return false; michael@0: } michael@0: michael@0: if (enumerable || (flags & JSITER_HIDDEN)) michael@0: return props->append(id); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: EnumerateNativeProperties(JSContext *cx, HandleObject pobj, unsigned flags, IdSet &ht, michael@0: AutoIdVector *props) michael@0: { michael@0: /* Collect any dense elements from this object. */ michael@0: size_t initlen = pobj->getDenseInitializedLength(); michael@0: const Value *vp = pobj->getDenseElements(); michael@0: for (size_t i = 0; i < initlen; ++i, ++vp) { michael@0: if (!vp->isMagic(JS_ELEMENTS_HOLE)) { michael@0: /* Dense arrays never get so large that i would not fit into an integer id. */ michael@0: if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* Collect any typed array elements from this object. */ michael@0: if (pobj->is()) { michael@0: size_t len = pobj->as().length(); michael@0: for (size_t i = 0; i < len; i++) { michael@0: if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: size_t initialLength = props->length(); michael@0: michael@0: /* Collect all unique properties from this object's scope. */ michael@0: Shape::Range r(pobj->lastProperty()); michael@0: for (; !r.empty(); r.popFront()) { michael@0: Shape &shape = r.front(); michael@0: michael@0: if (!Enumerate(cx, pobj, shape.propid(), shape.enumerable(), flags, ht, props)) michael@0: return false; michael@0: } michael@0: michael@0: ::Reverse(props->begin() + initialLength, props->end()); michael@0: return true; michael@0: } michael@0: michael@0: #ifdef JS_MORE_DETERMINISTIC michael@0: michael@0: struct SortComparatorIds michael@0: { michael@0: JSContext *const cx; michael@0: michael@0: SortComparatorIds(JSContext *cx) michael@0: : cx(cx) {} michael@0: michael@0: bool operator()(jsid a, jsid b, bool *lessOrEqualp) michael@0: { michael@0: /* Pick an arbitrary total order on jsids that is stable across executions. */ michael@0: RootedString astr(cx, IdToString(cx, a)); michael@0: if (!astr) michael@0: return false; michael@0: RootedString bstr(cx, IdToString(cx, b)); michael@0: if (!bstr) michael@0: return false; michael@0: michael@0: int32_t result; michael@0: if (!CompareStrings(cx, astr, bstr, &result)) michael@0: return false; michael@0: michael@0: *lessOrEqualp = (result <= 0); michael@0: return true; michael@0: } michael@0: }; michael@0: michael@0: #endif /* JS_MORE_DETERMINISTIC */ michael@0: michael@0: static bool michael@0: Snapshot(JSContext *cx, JSObject *pobj_, unsigned flags, AutoIdVector *props) michael@0: { michael@0: IdSet ht(cx); michael@0: if (!ht.init(32)) michael@0: return false; michael@0: michael@0: RootedObject pobj(cx, pobj_); michael@0: michael@0: do { michael@0: const Class *clasp = pobj->getClass(); michael@0: if (pobj->isNative() && michael@0: !pobj->getOps()->enumerate && michael@0: !(clasp->flags & JSCLASS_NEW_ENUMERATE)) { michael@0: if (!clasp->enumerate(cx, pobj)) michael@0: return false; michael@0: if (!EnumerateNativeProperties(cx, pobj, flags, ht, props)) michael@0: return false; michael@0: } else { michael@0: if (pobj->is()) { michael@0: AutoIdVector proxyProps(cx); michael@0: if (flags & JSITER_OWNONLY) { michael@0: if (flags & JSITER_HIDDEN) { michael@0: if (!Proxy::getOwnPropertyNames(cx, pobj, proxyProps)) michael@0: return false; michael@0: } else { michael@0: if (!Proxy::keys(cx, pobj, proxyProps)) michael@0: return false; michael@0: } michael@0: } else { michael@0: if (!Proxy::enumerate(cx, pobj, proxyProps)) michael@0: return false; michael@0: } michael@0: for (size_t n = 0, len = proxyProps.length(); n < len; n++) { michael@0: if (!Enumerate(cx, pobj, proxyProps[n], true, flags, ht, props)) michael@0: return false; michael@0: } michael@0: /* Proxy objects enumerate the prototype on their own, so we are done here. */ michael@0: break; michael@0: } michael@0: RootedValue state(cx); michael@0: RootedId id(cx); michael@0: JSIterateOp op = (flags & JSITER_HIDDEN) ? JSENUMERATE_INIT_ALL : JSENUMERATE_INIT; michael@0: if (!JSObject::enumerate(cx, pobj, op, &state, &id)) michael@0: return false; michael@0: if (state.isMagic(JS_NATIVE_ENUMERATE)) { michael@0: if (!EnumerateNativeProperties(cx, pobj, flags, ht, props)) michael@0: return false; michael@0: } else { michael@0: while (true) { michael@0: RootedId id(cx); michael@0: if (!JSObject::enumerate(cx, pobj, JSENUMERATE_NEXT, &state, &id)) michael@0: return false; michael@0: if (state.isNull()) michael@0: break; michael@0: if (!Enumerate(cx, pobj, id, true, flags, ht, props)) michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (flags & JSITER_OWNONLY) michael@0: break; michael@0: michael@0: } while ((pobj = pobj->getProto()) != nullptr); michael@0: michael@0: #ifdef JS_MORE_DETERMINISTIC michael@0: michael@0: /* michael@0: * In some cases the enumeration order for an object depends on the michael@0: * execution mode (interpreter vs. JIT), especially for native objects michael@0: * with a class enumerate hook (where resolving a property changes the michael@0: * resulting enumeration order). These aren't really bugs, but the michael@0: * differences can change the generated output and confuse correctness michael@0: * fuzzers, so we sort the ids if such a fuzzer is running. michael@0: * michael@0: * We don't do this in the general case because (a) doing so is slow, michael@0: * and (b) it also breaks the web, which expects enumeration order to michael@0: * follow the order in which properties are added, in certain cases. michael@0: * Since ECMA does not specify an enumeration order for objects, both michael@0: * behaviors are technically correct to do. michael@0: */ michael@0: michael@0: jsid *ids = props->begin(); michael@0: size_t n = props->length(); michael@0: michael@0: AutoIdVector tmp(cx); michael@0: if (!tmp.resize(n)) michael@0: return false; michael@0: PodCopy(tmp.begin(), ids, n); michael@0: michael@0: if (!MergeSort(ids, n, tmp.begin(), SortComparatorIds(cx))) michael@0: return false; michael@0: michael@0: #endif /* JS_MORE_DETERMINISTIC */ michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::VectorToIdArray(JSContext *cx, AutoIdVector &props, JSIdArray **idap) michael@0: { michael@0: JS_STATIC_ASSERT(sizeof(JSIdArray) > sizeof(jsid)); michael@0: size_t len = props.length(); michael@0: size_t idsz = len * sizeof(jsid); michael@0: size_t sz = (sizeof(JSIdArray) - sizeof(jsid)) + idsz; michael@0: JSIdArray *ida = static_cast(cx->malloc_(sz)); michael@0: if (!ida) michael@0: return false; michael@0: michael@0: ida->length = static_cast(len); michael@0: jsid *v = props.begin(); michael@0: for (int i = 0; i < ida->length; i++) michael@0: ida->vector[i].init(v[i]); michael@0: *idap = ida; michael@0: return true; michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: js::GetPropertyNames(JSContext *cx, JSObject *obj, unsigned flags, AutoIdVector *props) michael@0: { michael@0: return Snapshot(cx, obj, flags & (JSITER_OWNONLY | JSITER_HIDDEN), props); michael@0: } michael@0: michael@0: size_t sCustomIteratorCount = 0; michael@0: michael@0: static inline bool michael@0: GetCustomIterator(JSContext *cx, HandleObject obj, unsigned flags, MutableHandleValue vp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: /* Check whether we have a valid __iterator__ method. */ michael@0: HandlePropertyName name = cx->names().iteratorIntrinsic; michael@0: if (!JSObject::getProperty(cx, obj, obj, name, vp)) michael@0: return false; michael@0: michael@0: /* If there is no custom __iterator__ method, we are done here. */ michael@0: if (!vp.isObject()) { michael@0: vp.setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: if (!cx->runningWithTrustedPrincipals()) michael@0: ++sCustomIteratorCount; michael@0: michael@0: /* Otherwise call it and return that object. */ michael@0: Value arg = BooleanValue((flags & JSITER_FOREACH) == 0); michael@0: if (!Invoke(cx, ObjectValue(*obj), vp, 1, &arg, vp)) michael@0: return false; michael@0: if (vp.isPrimitive()) { michael@0: /* michael@0: * We are always coming from js::ValueToIterator, and we are no longer on michael@0: * trace, so the object we are iterating over is on top of the stack (-1). michael@0: */ michael@0: JSAutoByteString bytes; michael@0: if (!AtomToPrintableString(cx, name, &bytes)) michael@0: return false; michael@0: RootedValue val(cx, ObjectValue(*obj)); michael@0: js_ReportValueError2(cx, JSMSG_BAD_TRAP_RETURN_VALUE, michael@0: -1, val, js::NullPtr(), bytes.ptr()); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: static inline bool michael@0: Compare(T *a, T *b, size_t c) michael@0: { michael@0: size_t n = (c + size_t(7)) / size_t(8); michael@0: switch (c % 8) { michael@0: case 0: do { if (*a++ != *b++) return false; michael@0: case 7: if (*a++ != *b++) return false; michael@0: case 6: if (*a++ != *b++) return false; michael@0: case 5: if (*a++ != *b++) return false; michael@0: case 4: if (*a++ != *b++) return false; michael@0: case 3: if (*a++ != *b++) return false; michael@0: case 2: if (*a++ != *b++) return false; michael@0: case 1: if (*a++ != *b++) return false; michael@0: } while (--n > 0); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static inline PropertyIteratorObject * michael@0: NewPropertyIteratorObject(JSContext *cx, unsigned flags) michael@0: { michael@0: if (flags & JSITER_ENUMERATE) { michael@0: RootedTypeObject type(cx, cx->getNewType(&PropertyIteratorObject::class_, nullptr)); michael@0: if (!type) michael@0: return nullptr; michael@0: michael@0: JSObject *metadata = nullptr; michael@0: if (!NewObjectMetadata(cx, &metadata)) michael@0: return nullptr; michael@0: michael@0: const Class *clasp = &PropertyIteratorObject::class_; michael@0: RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, nullptr, nullptr, metadata, michael@0: ITERATOR_FINALIZE_KIND)); michael@0: if (!shape) michael@0: return nullptr; michael@0: michael@0: JSObject *obj = JSObject::create(cx, ITERATOR_FINALIZE_KIND, michael@0: GetInitialHeap(GenericObject, clasp), shape, type); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: JS_ASSERT(obj->numFixedSlots() == JSObject::ITER_CLASS_NFIXED_SLOTS); michael@0: return &obj->as(); michael@0: } michael@0: michael@0: JSObject *obj = NewBuiltinClassInstance(cx, &PropertyIteratorObject::class_); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: return &obj->as(); michael@0: } michael@0: michael@0: NativeIterator * michael@0: NativeIterator::allocateIterator(JSContext *cx, uint32_t slength, const AutoIdVector &props) michael@0: { michael@0: size_t plength = props.length(); michael@0: NativeIterator *ni = (NativeIterator *) michael@0: cx->malloc_(sizeof(NativeIterator) michael@0: + plength * sizeof(JSString *) michael@0: + slength * sizeof(Shape *)); michael@0: if (!ni) michael@0: return nullptr; michael@0: AutoValueVector strings(cx); michael@0: ni->props_array = ni->props_cursor = (HeapPtr *) (ni + 1); michael@0: ni->props_end = ni->props_array + plength; michael@0: if (plength) { michael@0: for (size_t i = 0; i < plength; i++) { michael@0: JSFlatString *str = IdToString(cx, props[i]); michael@0: if (!str || !strings.append(StringValue(str))) michael@0: return nullptr; michael@0: ni->props_array[i].init(str); michael@0: } michael@0: } michael@0: ni->next_ = nullptr; michael@0: ni->prev_ = nullptr; michael@0: return ni; michael@0: } michael@0: michael@0: NativeIterator * michael@0: NativeIterator::allocateSentinel(JSContext *cx) michael@0: { michael@0: NativeIterator *ni = (NativeIterator *)js_malloc(sizeof(NativeIterator)); michael@0: if (!ni) michael@0: return nullptr; michael@0: michael@0: PodZero(ni); michael@0: michael@0: ni->next_ = ni; michael@0: ni->prev_ = ni; michael@0: return ni; michael@0: } michael@0: michael@0: inline void michael@0: NativeIterator::init(JSObject *obj, JSObject *iterObj, unsigned flags, uint32_t slength, uint32_t key) michael@0: { michael@0: this->obj.init(obj); michael@0: this->iterObj_ = iterObj; michael@0: this->flags = flags; michael@0: this->shapes_array = (Shape **) this->props_end; michael@0: this->shapes_length = slength; michael@0: this->shapes_key = key; michael@0: } michael@0: michael@0: static inline void michael@0: RegisterEnumerator(JSContext *cx, PropertyIteratorObject *iterobj, NativeIterator *ni) michael@0: { michael@0: /* Register non-escaping native enumerators (for-in) with the current context. */ michael@0: if (ni->flags & JSITER_ENUMERATE) { michael@0: ni->link(cx->compartment()->enumerators); michael@0: michael@0: JS_ASSERT(!(ni->flags & JSITER_ACTIVE)); michael@0: ni->flags |= JSITER_ACTIVE; michael@0: } michael@0: } michael@0: michael@0: static inline bool michael@0: VectorToKeyIterator(JSContext *cx, HandleObject obj, unsigned flags, AutoIdVector &keys, michael@0: uint32_t slength, uint32_t key, MutableHandleValue vp) michael@0: { michael@0: JS_ASSERT(!(flags & JSITER_FOREACH)); michael@0: michael@0: if (obj) { michael@0: if (obj->hasSingletonType() && !obj->setIteratedSingleton(cx)) michael@0: return false; michael@0: types::MarkTypeObjectFlags(cx, obj, types::OBJECT_FLAG_ITERATED); michael@0: } michael@0: michael@0: Rooted iterobj(cx, NewPropertyIteratorObject(cx, flags)); michael@0: if (!iterobj) michael@0: return false; michael@0: michael@0: NativeIterator *ni = NativeIterator::allocateIterator(cx, slength, keys); michael@0: if (!ni) michael@0: return false; michael@0: ni->init(obj, iterobj, flags, slength, key); michael@0: michael@0: if (slength) { michael@0: /* michael@0: * Fill in the shape array from scratch. We can't use the array that was michael@0: * computed for the cache lookup earlier, as constructing iterobj could michael@0: * have triggered a shape-regenerating GC. Don't bother with regenerating michael@0: * the shape key; if such a GC *does* occur, we can only get hits through michael@0: * the one-slot lastNativeIterator cache. michael@0: */ michael@0: JSObject *pobj = obj; michael@0: size_t ind = 0; michael@0: do { michael@0: ni->shapes_array[ind++] = pobj->lastProperty(); michael@0: pobj = pobj->getProto(); michael@0: } while (pobj); michael@0: JS_ASSERT(ind == slength); michael@0: } michael@0: michael@0: iterobj->setNativeIterator(ni); michael@0: vp.setObject(*iterobj); michael@0: michael@0: RegisterEnumerator(cx, iterobj, ni); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::VectorToKeyIterator(JSContext *cx, HandleObject obj, unsigned flags, AutoIdVector &props, michael@0: MutableHandleValue vp) michael@0: { michael@0: return VectorToKeyIterator(cx, obj, flags, props, 0, 0, vp); michael@0: } michael@0: michael@0: bool michael@0: js::VectorToValueIterator(JSContext *cx, HandleObject obj, unsigned flags, AutoIdVector &keys, michael@0: MutableHandleValue vp) michael@0: { michael@0: JS_ASSERT(flags & JSITER_FOREACH); michael@0: michael@0: if (obj) { michael@0: if (obj->hasSingletonType() && !obj->setIteratedSingleton(cx)) michael@0: return false; michael@0: types::MarkTypeObjectFlags(cx, obj, types::OBJECT_FLAG_ITERATED); michael@0: } michael@0: michael@0: Rooted iterobj(cx, NewPropertyIteratorObject(cx, flags)); michael@0: if (!iterobj) michael@0: return false; michael@0: michael@0: NativeIterator *ni = NativeIterator::allocateIterator(cx, 0, keys); michael@0: if (!ni) michael@0: return false; michael@0: ni->init(obj, iterobj, flags, 0, 0); michael@0: michael@0: iterobj->setNativeIterator(ni); michael@0: vp.setObject(*iterobj); michael@0: michael@0: RegisterEnumerator(cx, iterobj, ni); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::EnumeratedIdVectorToIterator(JSContext *cx, HandleObject obj, unsigned flags, michael@0: AutoIdVector &props, MutableHandleValue vp) michael@0: { michael@0: if (!(flags & JSITER_FOREACH)) michael@0: return VectorToKeyIterator(cx, obj, flags, props, vp); michael@0: michael@0: return VectorToValueIterator(cx, obj, flags, props, vp); michael@0: } michael@0: michael@0: static inline void michael@0: UpdateNativeIterator(NativeIterator *ni, JSObject *obj) michael@0: { michael@0: // Update the object for which the native iterator is associated, so michael@0: // SuppressDeletedPropertyHelper will recognize the iterator as a match. michael@0: ni->obj = obj; michael@0: } michael@0: michael@0: bool michael@0: js::GetIterator(JSContext *cx, HandleObject obj, unsigned flags, MutableHandleValue vp) michael@0: { michael@0: Vector shapes(cx); michael@0: uint32_t key = 0; michael@0: michael@0: bool keysOnly = (flags == JSITER_ENUMERATE); michael@0: michael@0: if (obj) { michael@0: if (JSIteratorOp op = obj->getClass()->ext.iteratorObject) { michael@0: JSObject *iterobj = op(cx, obj, !(flags & JSITER_FOREACH)); michael@0: if (!iterobj) michael@0: return false; michael@0: vp.setObject(*iterobj); michael@0: return true; michael@0: } michael@0: michael@0: if (keysOnly) { michael@0: /* michael@0: * Check to see if this is the same as the most recent object which michael@0: * was iterated over. We don't explicitly check for shapeless michael@0: * objects here, as they are not inserted into the cache and michael@0: * will result in a miss. michael@0: */ michael@0: PropertyIteratorObject *last = cx->runtime()->nativeIterCache.last; michael@0: if (last) { michael@0: NativeIterator *lastni = last->getNativeIterator(); michael@0: if (!(lastni->flags & (JSITER_ACTIVE|JSITER_UNREUSABLE)) && michael@0: obj->isNative() && michael@0: obj->hasEmptyElements() && michael@0: obj->lastProperty() == lastni->shapes_array[0]) michael@0: { michael@0: JSObject *proto = obj->getProto(); michael@0: if (proto->isNative() && michael@0: proto->hasEmptyElements() && michael@0: proto->lastProperty() == lastni->shapes_array[1] && michael@0: !proto->getProto()) michael@0: { michael@0: vp.setObject(*last); michael@0: UpdateNativeIterator(lastni, obj); michael@0: RegisterEnumerator(cx, last, lastni); michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * The iterator object for JSITER_ENUMERATE never escapes, so we michael@0: * don't care for the proper parent/proto to be set. This also michael@0: * allows us to re-use a previous iterator object that is not michael@0: * currently active. michael@0: */ michael@0: { michael@0: JSObject *pobj = obj; michael@0: do { michael@0: if (!pobj->isNative() || michael@0: !pobj->hasEmptyElements() || michael@0: pobj->is() || michael@0: pobj->hasUncacheableProto() || michael@0: pobj->getOps()->enumerate || michael@0: pobj->getClass()->enumerate != JS_EnumerateStub || michael@0: pobj->nativeContainsPure(cx->names().iteratorIntrinsic)) michael@0: { michael@0: shapes.clear(); michael@0: goto miss; michael@0: } michael@0: Shape *shape = pobj->lastProperty(); michael@0: key = (key + (key << 16)) ^ (uintptr_t(shape) >> 3); michael@0: if (!shapes.append(shape)) michael@0: return false; michael@0: pobj = pobj->getProto(); michael@0: } while (pobj); michael@0: } michael@0: michael@0: PropertyIteratorObject *iterobj = cx->runtime()->nativeIterCache.get(key); michael@0: if (iterobj) { michael@0: NativeIterator *ni = iterobj->getNativeIterator(); michael@0: if (!(ni->flags & (JSITER_ACTIVE|JSITER_UNREUSABLE)) && michael@0: ni->shapes_key == key && michael@0: ni->shapes_length == shapes.length() && michael@0: Compare(ni->shapes_array, shapes.begin(), ni->shapes_length)) { michael@0: vp.setObject(*iterobj); michael@0: michael@0: UpdateNativeIterator(ni, obj); michael@0: RegisterEnumerator(cx, iterobj, ni); michael@0: if (shapes.length() == 2) michael@0: cx->runtime()->nativeIterCache.last = iterobj; michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: miss: michael@0: if (obj->is()) michael@0: return Proxy::iterate(cx, obj, flags, vp); michael@0: michael@0: if (!GetCustomIterator(cx, obj, flags, vp)) michael@0: return false; michael@0: if (!vp.isUndefined()) michael@0: return true; michael@0: } michael@0: michael@0: /* NB: for (var p in null) succeeds by iterating over no properties. */ michael@0: michael@0: AutoIdVector keys(cx); michael@0: if (flags & JSITER_FOREACH) { michael@0: if (MOZ_LIKELY(obj != nullptr) && !Snapshot(cx, obj, flags, &keys)) michael@0: return false; michael@0: JS_ASSERT(shapes.empty()); michael@0: if (!VectorToValueIterator(cx, obj, flags, keys, vp)) michael@0: return false; michael@0: } else { michael@0: if (MOZ_LIKELY(obj != nullptr) && !Snapshot(cx, obj, flags, &keys)) michael@0: return false; michael@0: if (!VectorToKeyIterator(cx, obj, flags, keys, shapes.length(), key, vp)) michael@0: return false; michael@0: } michael@0: michael@0: PropertyIteratorObject *iterobj = &vp.toObject().as(); michael@0: michael@0: /* Cache the iterator object if possible. */ michael@0: if (shapes.length()) michael@0: cx->runtime()->nativeIterCache.set(key, iterobj); michael@0: michael@0: if (shapes.length() == 2) michael@0: cx->runtime()->nativeIterCache.last = iterobj; michael@0: return true; michael@0: } michael@0: michael@0: JSObject * michael@0: js::GetIteratorObject(JSContext *cx, HandleObject obj, uint32_t flags) michael@0: { michael@0: RootedValue value(cx); michael@0: if (!GetIterator(cx, obj, flags, &value)) michael@0: return nullptr; michael@0: return &value.toObject(); michael@0: } michael@0: michael@0: JSObject * michael@0: js::CreateItrResultObject(JSContext *cx, HandleValue value, bool done) michael@0: { michael@0: // FIXME: We can cache the iterator result object shape somewhere. michael@0: AssertHeapIsIdle(cx); michael@0: michael@0: RootedObject proto(cx, cx->global()->getOrCreateObjectPrototype(cx)); michael@0: if (!proto) michael@0: return nullptr; michael@0: michael@0: RootedObject obj(cx, NewObjectWithGivenProto(cx, &JSObject::class_, proto, cx->global())); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: if (!JSObject::defineProperty(cx, obj, cx->names().value, value)) michael@0: return nullptr; michael@0: michael@0: RootedValue doneBool(cx, BooleanValue(done)); michael@0: if (!JSObject::defineProperty(cx, obj, cx->names().done, doneBool)) michael@0: return nullptr; michael@0: michael@0: return obj; michael@0: } michael@0: michael@0: bool michael@0: js_ThrowStopIteration(JSContext *cx) michael@0: { michael@0: JS_ASSERT(!JS_IsExceptionPending(cx)); michael@0: michael@0: // StopIteration isn't a constructor, but it's stored in GlobalObject michael@0: // as one, out of laziness. Hence the GetBuiltinConstructor call here. michael@0: RootedObject ctor(cx); michael@0: if (GetBuiltinConstructor(cx, JSProto_StopIteration, &ctor)) michael@0: cx->setPendingException(ObjectValue(*ctor)); michael@0: return false; michael@0: } michael@0: michael@0: /*** Iterator objects ****************************************************************************/ michael@0: michael@0: bool michael@0: js::IteratorConstructor(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() == 0) { michael@0: js_ReportMissingArg(cx, args.calleev(), 0); michael@0: return false; michael@0: } michael@0: michael@0: bool keyonly = false; michael@0: if (args.length() >= 2) michael@0: keyonly = ToBoolean(args[1]); michael@0: unsigned flags = JSITER_OWNONLY | (keyonly ? 0 : (JSITER_FOREACH | JSITER_KEYVALUE)); michael@0: michael@0: if (!ValueToIterator(cx, flags, args[0])) michael@0: return false; michael@0: args.rval().set(args[0]); michael@0: return true; michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: IsIterator(HandleValue v) michael@0: { michael@0: return v.isObject() && v.toObject().hasClass(&PropertyIteratorObject::class_); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: iterator_next_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(IsIterator(args.thisv())); michael@0: michael@0: RootedObject thisObj(cx, &args.thisv().toObject()); michael@0: michael@0: if (!js_IteratorMore(cx, thisObj, args.rval())) michael@0: return false; michael@0: michael@0: if (!args.rval().toBoolean()) { michael@0: js_ThrowStopIteration(cx); michael@0: return false; michael@0: } michael@0: michael@0: return js_IteratorNext(cx, thisObj, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: iterator_next(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: static const JSFunctionSpec iterator_methods[] = { michael@0: JS_SELF_HOSTED_FN("@@iterator", "LegacyIteratorShim", 0, 0), michael@0: JS_FN("next", iterator_next, 0, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: static JSObject * michael@0: iterator_iteratorObject(JSContext *cx, HandleObject obj, bool keysonly) michael@0: { michael@0: return obj; michael@0: } michael@0: michael@0: size_t michael@0: PropertyIteratorObject::sizeOfMisc(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: return mallocSizeOf(getPrivate()); michael@0: } michael@0: michael@0: void michael@0: PropertyIteratorObject::trace(JSTracer *trc, JSObject *obj) michael@0: { michael@0: if (NativeIterator *ni = obj->as().getNativeIterator()) michael@0: ni->mark(trc); michael@0: } michael@0: michael@0: void michael@0: PropertyIteratorObject::finalize(FreeOp *fop, JSObject *obj) michael@0: { michael@0: if (NativeIterator *ni = obj->as().getNativeIterator()) michael@0: fop->free_(ni); michael@0: } michael@0: michael@0: const Class PropertyIteratorObject::class_ = { michael@0: "Iterator", michael@0: JSCLASS_IMPLEMENTS_BARRIERS | michael@0: JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator) | michael@0: JSCLASS_HAS_PRIVATE | michael@0: JSCLASS_BACKGROUND_FINALIZE, michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: finalize, michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: trace, michael@0: JS_NULL_CLASS_SPEC, michael@0: { michael@0: nullptr, /* outerObject */ michael@0: nullptr, /* innerObject */ michael@0: iterator_iteratorObject, michael@0: } michael@0: }; michael@0: michael@0: enum { michael@0: ArrayIteratorSlotIteratedObject, michael@0: ArrayIteratorSlotNextIndex, michael@0: ArrayIteratorSlotItemKind, michael@0: ArrayIteratorSlotCount michael@0: }; michael@0: michael@0: const Class ArrayIteratorObject::class_ = { michael@0: "Array Iterator", michael@0: JSCLASS_IMPLEMENTS_BARRIERS | michael@0: JSCLASS_HAS_RESERVED_SLOTS(ArrayIteratorSlotCount), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: nullptr /* finalize */ michael@0: }; michael@0: michael@0: static const JSFunctionSpec array_iterator_methods[] = { michael@0: JS_SELF_HOSTED_FN("@@iterator", "ArrayIteratorIdentity", 0, 0), michael@0: JS_SELF_HOSTED_FN("next", "ArrayIteratorNext", 0, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: static const Class StringIteratorPrototypeClass = { michael@0: "String Iterator", michael@0: JSCLASS_IMPLEMENTS_BARRIERS, michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: nullptr /* finalize */ michael@0: }; michael@0: michael@0: enum { michael@0: StringIteratorSlotIteratedObject, michael@0: StringIteratorSlotNextIndex, michael@0: StringIteratorSlotCount michael@0: }; michael@0: michael@0: const Class StringIteratorObject::class_ = { michael@0: "String Iterator", michael@0: JSCLASS_IMPLEMENTS_BARRIERS | michael@0: JSCLASS_HAS_RESERVED_SLOTS(StringIteratorSlotCount), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: nullptr /* finalize */ michael@0: }; michael@0: michael@0: static const JSFunctionSpec string_iterator_methods[] = { michael@0: JS_SELF_HOSTED_FN("@@iterator", "StringIteratorIdentity", 0, 0), michael@0: JS_SELF_HOSTED_FN("next", "StringIteratorNext", 0, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: static bool michael@0: CloseLegacyGenerator(JSContext *cx, HandleObject genobj); michael@0: michael@0: bool michael@0: js::ValueToIterator(JSContext *cx, unsigned flags, MutableHandleValue vp) michael@0: { michael@0: /* JSITER_KEYVALUE must always come with JSITER_FOREACH */ michael@0: JS_ASSERT_IF(flags & JSITER_KEYVALUE, flags & JSITER_FOREACH); michael@0: michael@0: /* michael@0: * Make sure the more/next state machine doesn't get stuck. A value might michael@0: * be left in iterValue when a trace is left due to an interrupt after michael@0: * JSOP_MOREITER but before the value is picked up by FOR*. michael@0: */ michael@0: cx->iterValue.setMagic(JS_NO_ITER_VALUE); michael@0: michael@0: RootedObject obj(cx); michael@0: if (vp.isObject()) { michael@0: /* Common case. */ michael@0: obj = &vp.toObject(); michael@0: } else { michael@0: /* michael@0: * Enumerating over null and undefined gives an empty enumerator, so michael@0: * that |for (var p in ) ;| never executes michael@0: * , per ES5 12.6.4. michael@0: */ michael@0: if (!(flags & JSITER_ENUMERATE) || !vp.isNullOrUndefined()) { michael@0: obj = ToObject(cx, vp); michael@0: if (!obj) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return GetIterator(cx, obj, flags, vp); michael@0: } michael@0: michael@0: bool michael@0: js::CloseIterator(JSContext *cx, HandleObject obj) michael@0: { michael@0: cx->iterValue.setMagic(JS_NO_ITER_VALUE); michael@0: michael@0: if (obj->is()) { michael@0: /* Remove enumerators from the active list, which is a stack. */ michael@0: NativeIterator *ni = obj->as().getNativeIterator(); michael@0: michael@0: if (ni->flags & JSITER_ENUMERATE) { michael@0: ni->unlink(); michael@0: michael@0: JS_ASSERT(ni->flags & JSITER_ACTIVE); michael@0: ni->flags &= ~JSITER_ACTIVE; michael@0: michael@0: /* michael@0: * Reset the enumerator; it may still be in the cached iterators michael@0: * for this thread, and can be reused. michael@0: */ michael@0: ni->props_cursor = ni->props_array; michael@0: } michael@0: } else if (obj->is()) { michael@0: return CloseLegacyGenerator(cx, obj); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::UnwindIteratorForException(JSContext *cx, HandleObject obj) michael@0: { michael@0: RootedValue v(cx); michael@0: bool getOk = cx->getPendingException(&v); michael@0: cx->clearPendingException(); michael@0: if (!CloseIterator(cx, obj)) michael@0: return false; michael@0: if (!getOk) michael@0: return false; michael@0: cx->setPendingException(v); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: js::UnwindIteratorForUncatchableException(JSContext *cx, JSObject *obj) michael@0: { michael@0: if (obj->is()) { michael@0: NativeIterator *ni = obj->as().getNativeIterator(); michael@0: if (ni->flags & JSITER_ENUMERATE) michael@0: ni->unlink(); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Suppress enumeration of deleted properties. This function must be called michael@0: * when a property is deleted and there might be active enumerators. michael@0: * michael@0: * We maintain a list of active non-escaping for-in enumerators. To suppress michael@0: * a property, we check whether each active enumerator contains the (obj, id) michael@0: * pair and has not yet enumerated |id|. If so, and |id| is the next property, michael@0: * we simply advance the cursor. Otherwise, we delete |id| from the list. michael@0: * michael@0: * We do not suppress enumeration of a property deleted along an object's michael@0: * prototype chain. Only direct deletions on the object are handled. michael@0: * michael@0: * This function can suppress multiple properties at once. The |predicate| michael@0: * argument is an object which can be called on an id and returns true or michael@0: * false. It also must have a method |matchesAtMostOne| which allows us to michael@0: * stop searching after the first deletion if true. michael@0: */ michael@0: template michael@0: static bool michael@0: SuppressDeletedPropertyHelper(JSContext *cx, HandleObject obj, StringPredicate predicate) michael@0: { michael@0: NativeIterator *enumeratorList = cx->compartment()->enumerators; michael@0: NativeIterator *ni = enumeratorList->next(); michael@0: michael@0: while (ni != enumeratorList) { michael@0: again: michael@0: /* This only works for identified suppressed keys, not values. */ michael@0: if (ni->isKeyIter() && ni->obj == obj && ni->props_cursor < ni->props_end) { michael@0: /* Check whether id is still to come. */ michael@0: HeapPtr *props_cursor = ni->current(); michael@0: HeapPtr *props_end = ni->end(); michael@0: for (HeapPtr *idp = props_cursor; idp < props_end; ++idp) { michael@0: if (predicate(*idp)) { michael@0: /* michael@0: * Check whether another property along the prototype chain michael@0: * became visible as a result of this deletion. michael@0: */ michael@0: RootedObject proto(cx); michael@0: if (!JSObject::getProto(cx, obj, &proto)) michael@0: return false; michael@0: if (proto) { michael@0: RootedObject obj2(cx); michael@0: RootedShape prop(cx); michael@0: RootedId id(cx); michael@0: RootedValue idv(cx, StringValue(*idp)); michael@0: if (!ValueToId(cx, idv, &id)) michael@0: return false; michael@0: if (!JSObject::lookupGeneric(cx, proto, id, &obj2, &prop)) michael@0: return false; michael@0: if (prop) { michael@0: unsigned attrs; michael@0: if (obj2->isNative()) michael@0: attrs = GetShapeAttributes(obj2, prop); michael@0: else if (!JSObject::getGenericAttributes(cx, obj2, id, &attrs)) michael@0: return false; michael@0: michael@0: if (attrs & JSPROP_ENUMERATE) michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * If lookupProperty or getAttributes above removed a property from michael@0: * ni, start over. michael@0: */ michael@0: if (props_end != ni->props_end || props_cursor != ni->props_cursor) michael@0: goto again; michael@0: michael@0: /* michael@0: * No property along the prototype chain stepped in to take the michael@0: * property's place, so go ahead and delete id from the list. michael@0: * If it is the next property to be enumerated, just skip it. michael@0: */ michael@0: if (idp == props_cursor) { michael@0: ni->incCursor(); michael@0: } else { michael@0: for (HeapPtr *p = idp; p + 1 != props_end; p++) michael@0: *p = *(p + 1); michael@0: ni->props_end = ni->end() - 1; michael@0: michael@0: /* michael@0: * This invokes the pre barrier on this element, since michael@0: * it's no longer going to be marked, and ensures that michael@0: * any existing remembered set entry will be dropped. michael@0: */ michael@0: *ni->props_end = nullptr; michael@0: } michael@0: michael@0: /* Don't reuse modified native iterators. */ michael@0: ni->flags |= JSITER_UNREUSABLE; michael@0: michael@0: if (predicate.matchesAtMostOne()) michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: ni = ni->next(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: class SingleStringPredicate { michael@0: Handle str; michael@0: public: michael@0: SingleStringPredicate(Handle str) : str(str) {} michael@0: michael@0: bool operator()(JSFlatString *str) { return EqualStrings(str, this->str); } michael@0: bool matchesAtMostOne() { return true; } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: bool michael@0: js_SuppressDeletedProperty(JSContext *cx, HandleObject obj, jsid id) michael@0: { michael@0: Rooted str(cx, IdToString(cx, id)); michael@0: if (!str) michael@0: return false; michael@0: return SuppressDeletedPropertyHelper(cx, obj, SingleStringPredicate(str)); michael@0: } michael@0: michael@0: bool michael@0: js_SuppressDeletedElement(JSContext *cx, HandleObject obj, uint32_t index) michael@0: { michael@0: RootedId id(cx); michael@0: if (!IndexToId(cx, index, &id)) michael@0: return false; michael@0: return js_SuppressDeletedProperty(cx, obj, id); michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: class IndexRangePredicate { michael@0: uint32_t begin, end; michael@0: michael@0: public: michael@0: IndexRangePredicate(uint32_t begin, uint32_t end) : begin(begin), end(end) {} michael@0: michael@0: bool operator()(JSFlatString *str) { michael@0: uint32_t index; michael@0: return str->isIndex(&index) && begin <= index && index < end; michael@0: } michael@0: michael@0: bool matchesAtMostOne() { return false; } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: bool michael@0: js_SuppressDeletedElements(JSContext *cx, HandleObject obj, uint32_t begin, uint32_t end) michael@0: { michael@0: return SuppressDeletedPropertyHelper(cx, obj, IndexRangePredicate(begin, end)); michael@0: } michael@0: michael@0: bool michael@0: js_IteratorMore(JSContext *cx, HandleObject iterobj, MutableHandleValue rval) michael@0: { michael@0: /* Fast path for native iterators */ michael@0: NativeIterator *ni = nullptr; michael@0: if (iterobj->is()) { michael@0: /* Key iterators are handled by fast-paths. */ michael@0: ni = iterobj->as().getNativeIterator(); michael@0: bool more = ni->props_cursor < ni->props_end; michael@0: if (ni->isKeyIter() || !more) { michael@0: rval.setBoolean(more); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: /* We might still have a pending value. */ michael@0: if (!cx->iterValue.isMagic(JS_NO_ITER_VALUE)) { michael@0: rval.setBoolean(true); michael@0: return true; michael@0: } michael@0: michael@0: /* We're reentering below and can call anything. */ michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: /* Fetch and cache the next value from the iterator. */ michael@0: if (ni) { michael@0: JS_ASSERT(!ni->isKeyIter()); michael@0: RootedId id(cx); michael@0: RootedValue current(cx, StringValue(*ni->current())); michael@0: if (!ValueToId(cx, current, &id)) michael@0: return false; michael@0: ni->incCursor(); michael@0: RootedObject obj(cx, ni->obj); michael@0: if (!JSObject::getGeneric(cx, obj, obj, id, rval)) michael@0: return false; michael@0: if ((ni->flags & JSITER_KEYVALUE) && !NewKeyValuePair(cx, id, rval, rval)) michael@0: return false; michael@0: } else { michael@0: /* Call the iterator object's .next method. */ michael@0: if (!JSObject::getProperty(cx, iterobj, iterobj, cx->names().next, rval)) michael@0: return false; michael@0: if (!Invoke(cx, ObjectValue(*iterobj), rval, 0, nullptr, rval)) { michael@0: /* Check for StopIteration. */ michael@0: if (!cx->isExceptionPending()) michael@0: return false; michael@0: RootedValue exception(cx); michael@0: if (!cx->getPendingException(&exception)) michael@0: return false; michael@0: if (!JS_IsStopIteration(exception)) michael@0: return false; michael@0: michael@0: cx->clearPendingException(); michael@0: cx->iterValue.setMagic(JS_NO_ITER_VALUE); michael@0: rval.setBoolean(false); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: /* Cache the value returned by iterobj.next() so js_IteratorNext() can find it. */ michael@0: JS_ASSERT(!rval.isMagic(JS_NO_ITER_VALUE)); michael@0: cx->iterValue = rval; michael@0: rval.setBoolean(true); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js_IteratorNext(JSContext *cx, HandleObject iterobj, MutableHandleValue rval) michael@0: { michael@0: /* Fast path for native iterators */ michael@0: if (iterobj->is()) { michael@0: /* michael@0: * Implement next directly as all the methods of the native iterator are michael@0: * read-only and permanent. michael@0: */ michael@0: NativeIterator *ni = iterobj->as().getNativeIterator(); michael@0: if (ni->isKeyIter()) { michael@0: JS_ASSERT(ni->props_cursor < ni->props_end); michael@0: rval.setString(*ni->current()); michael@0: ni->incCursor(); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: JS_ASSERT(!cx->iterValue.isMagic(JS_NO_ITER_VALUE)); michael@0: rval.set(cx->iterValue); michael@0: cx->iterValue.setMagic(JS_NO_ITER_VALUE); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: stopiter_hasInstance(JSContext *cx, HandleObject obj, MutableHandleValue v, bool *bp) michael@0: { michael@0: *bp = JS_IsStopIteration(v); michael@0: return true; michael@0: } michael@0: michael@0: const Class StopIterationObject::class_ = { michael@0: "StopIteration", michael@0: JSCLASS_HAS_CACHED_PROTO(JSProto_StopIteration), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: nullptr, /* finalize */ michael@0: nullptr, /* call */ michael@0: stopiter_hasInstance, michael@0: nullptr /* construct */ michael@0: }; michael@0: michael@0: bool michael@0: ForOfIterator::init(HandleValue iterable, NonIterableBehavior nonIterableBehavior) michael@0: { michael@0: JSContext *cx = cx_; michael@0: RootedObject iterableObj(cx, ToObject(cx, iterable)); michael@0: if (!iterableObj) michael@0: return false; michael@0: michael@0: JS_ASSERT(index == NOT_ARRAY); michael@0: michael@0: // Check the PIC first for a match. michael@0: if (iterableObj->is()) { michael@0: ForOfPIC::Chain *stubChain = ForOfPIC::getOrCreate(cx); michael@0: if (!stubChain) michael@0: return false; michael@0: michael@0: bool optimized; michael@0: if (!stubChain->tryOptimizeArray(cx, iterableObj, &optimized)) michael@0: return false; michael@0: michael@0: if (optimized) { michael@0: // Got optimized stub. Array is optimizable. michael@0: index = 0; michael@0: iterator = iterableObj; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: JS_ASSERT(index == NOT_ARRAY); michael@0: michael@0: // The iterator is the result of calling obj[@@iterator](). michael@0: InvokeArgs args(cx); michael@0: if (!args.init(0)) michael@0: return false; michael@0: args.setThis(ObjectValue(*iterableObj)); michael@0: michael@0: RootedValue callee(cx); michael@0: if (!JSObject::getProperty(cx, iterableObj, iterableObj, cx->names().std_iterator, &callee)) michael@0: return false; michael@0: michael@0: // Throw if obj[@@iterator] isn't callable if we were asked to do so. michael@0: // js::Invoke is about to check for this kind of error anyway, but it would michael@0: // throw an inscrutable error message about |method| rather than this nice michael@0: // one about |obj|. michael@0: if (!callee.isObject() || !callee.toObject().isCallable()) { michael@0: if (nonIterableBehavior == AllowNonIterable) michael@0: return true; michael@0: char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, iterable, NullPtr()); michael@0: if (!bytes) michael@0: return false; michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, bytes); michael@0: js_free(bytes); michael@0: return false; michael@0: } michael@0: michael@0: args.setCallee(callee); michael@0: if (!Invoke(cx, args)) michael@0: return false; michael@0: michael@0: iterator = ToObject(cx, args.rval()); michael@0: if (!iterator) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: inline bool michael@0: ForOfIterator::nextFromOptimizedArray(MutableHandleValue vp, bool *done) michael@0: { michael@0: JS_ASSERT(index != NOT_ARRAY); michael@0: michael@0: if (!CheckForInterrupt(cx_)) michael@0: return false; michael@0: michael@0: JS_ASSERT(iterator->isNative()); michael@0: JS_ASSERT(iterator->is()); michael@0: michael@0: if (index >= iterator->as().length()) { michael@0: vp.setUndefined(); michael@0: *done = true; michael@0: return true; michael@0: } michael@0: *done = false; michael@0: michael@0: // Try to get array element via direct access. michael@0: if (index < iterator->getDenseInitializedLength()) { michael@0: vp.set(iterator->getDenseElement(index)); michael@0: if (!vp.isMagic(JS_ELEMENTS_HOLE)) { michael@0: ++index; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return JSObject::getElement(cx_, iterator, iterator, index++, vp); michael@0: } michael@0: michael@0: bool michael@0: ForOfIterator::next(MutableHandleValue vp, bool *done) michael@0: { michael@0: JS_ASSERT(iterator); michael@0: michael@0: if (index != NOT_ARRAY) { michael@0: ForOfPIC::Chain *stubChain = ForOfPIC::getOrCreate(cx_); michael@0: if (!stubChain) michael@0: return false; michael@0: michael@0: if (stubChain->isArrayNextStillSane()) michael@0: return nextFromOptimizedArray(vp, done); michael@0: michael@0: // ArrayIterator.prototype.next changed, materialize a proper michael@0: // ArrayIterator instance and fall through to slowpath case. michael@0: if (!materializeArrayIterator()) michael@0: return false; michael@0: } michael@0: michael@0: RootedValue method(cx_); michael@0: if (!JSObject::getProperty(cx_, iterator, iterator, cx_->names().next, &method)) michael@0: return false; michael@0: michael@0: InvokeArgs args(cx_); michael@0: if (!args.init(1)) michael@0: return false; michael@0: args.setCallee(method); michael@0: args.setThis(ObjectValue(*iterator)); michael@0: args[0].setUndefined(); michael@0: if (!Invoke(cx_, args)) michael@0: return false; michael@0: michael@0: RootedObject resultObj(cx_, ToObject(cx_, args.rval())); michael@0: if (!resultObj) michael@0: return false; michael@0: RootedValue doneVal(cx_); michael@0: if (!JSObject::getProperty(cx_, resultObj, resultObj, cx_->names().done, &doneVal)) michael@0: return false; michael@0: *done = ToBoolean(doneVal); michael@0: if (*done) { michael@0: vp.setUndefined(); michael@0: return true; michael@0: } michael@0: return JSObject::getProperty(cx_, resultObj, resultObj, cx_->names().value, vp); michael@0: } michael@0: michael@0: bool michael@0: ForOfIterator::materializeArrayIterator() michael@0: { michael@0: JS_ASSERT(index != NOT_ARRAY); michael@0: michael@0: const char *nameString = "ArrayValuesAt"; michael@0: michael@0: RootedAtom name(cx_, Atomize(cx_, nameString, strlen(nameString))); michael@0: if (!name) michael@0: return false; michael@0: michael@0: RootedValue val(cx_); michael@0: if (!cx_->global()->getSelfHostedFunction(cx_, name, name, 1, &val)) michael@0: return false; michael@0: michael@0: InvokeArgs args(cx_); michael@0: if (!args.init(1)) michael@0: return false; michael@0: args.setCallee(val); michael@0: args.setThis(ObjectValue(*iterator)); michael@0: args[0].set(Int32Value(index)); michael@0: if (!Invoke(cx_, args)) michael@0: return false; michael@0: michael@0: index = NOT_ARRAY; michael@0: // Result of call to ArrayValuesAt must be an object. michael@0: iterator = &args.rval().toObject(); michael@0: return true; michael@0: } michael@0: michael@0: /*** Generators **********************************************************************************/ michael@0: michael@0: template michael@0: static void michael@0: FinalizeGenerator(FreeOp *fop, JSObject *obj) michael@0: { michael@0: JS_ASSERT(obj->is()); michael@0: JSGenerator *gen = obj->as().getGenerator(); michael@0: JS_ASSERT(gen); michael@0: // gen is open when a script has not called its close method while michael@0: // explicitly manipulating it. michael@0: JS_ASSERT(gen->state == JSGEN_NEWBORN || michael@0: gen->state == JSGEN_CLOSED || michael@0: gen->state == JSGEN_OPEN); michael@0: // If gen->state is JSGEN_CLOSED, gen->fp may be nullptr. michael@0: if (gen->fp) michael@0: JS_POISON(gen->fp, JS_SWEPT_FRAME_PATTERN, sizeof(InterpreterFrame)); michael@0: JS_POISON(gen, JS_SWEPT_FRAME_PATTERN, sizeof(JSGenerator)); michael@0: fop->free_(gen); michael@0: } michael@0: michael@0: static void michael@0: MarkGeneratorFrame(JSTracer *trc, JSGenerator *gen) michael@0: { michael@0: MarkValueRange(trc, michael@0: HeapValueify(gen->fp->generatorArgsSnapshotBegin()), michael@0: HeapValueify(gen->fp->generatorArgsSnapshotEnd()), michael@0: "Generator Floating Args"); michael@0: gen->fp->mark(trc); michael@0: MarkValueRange(trc, michael@0: HeapValueify(gen->fp->generatorSlotsSnapshotBegin()), michael@0: HeapValueify(gen->regs.sp), michael@0: "Generator Floating Stack"); michael@0: } michael@0: michael@0: static void michael@0: GeneratorWriteBarrierPre(JSContext *cx, JSGenerator *gen) michael@0: { michael@0: JS::Zone *zone = cx->zone(); michael@0: if (zone->needsBarrier()) michael@0: MarkGeneratorFrame(zone->barrierTracer(), gen); michael@0: } michael@0: michael@0: static void michael@0: GeneratorWriteBarrierPost(JSContext *cx, JSGenerator *gen) michael@0: { michael@0: #ifdef JSGC_GENERATIONAL michael@0: cx->runtime()->gcStoreBuffer.putWholeCell(gen->obj); michael@0: #endif michael@0: } michael@0: michael@0: /* michael@0: * Only mark generator frames/slots when the generator is not active on the michael@0: * stack or closed. Barriers when copying onto the stack or closing preserve michael@0: * gc invariants. michael@0: */ michael@0: static bool michael@0: GeneratorHasMarkableFrame(JSGenerator *gen) michael@0: { michael@0: return gen->state == JSGEN_NEWBORN || gen->state == JSGEN_OPEN; michael@0: } michael@0: michael@0: /* michael@0: * When a generator is closed, the GC things reachable from the contained frame michael@0: * and slots become unreachable and thus require a write barrier. michael@0: */ michael@0: static void michael@0: SetGeneratorClosed(JSContext *cx, JSGenerator *gen) michael@0: { michael@0: JS_ASSERT(gen->state != JSGEN_CLOSED); michael@0: if (GeneratorHasMarkableFrame(gen)) michael@0: GeneratorWriteBarrierPre(cx, gen); michael@0: gen->state = JSGEN_CLOSED; michael@0: michael@0: #ifdef DEBUG michael@0: MakeRangeGCSafe(gen->fp->generatorArgsSnapshotBegin(), michael@0: gen->fp->generatorArgsSnapshotEnd()); michael@0: MakeRangeGCSafe(gen->fp->generatorSlotsSnapshotBegin(), michael@0: gen->regs.sp); michael@0: PodZero(&gen->regs, 1); michael@0: gen->fp = nullptr; michael@0: #endif michael@0: } michael@0: michael@0: template michael@0: static void michael@0: TraceGenerator(JSTracer *trc, JSObject *obj) michael@0: { michael@0: JS_ASSERT(obj->is()); michael@0: JSGenerator *gen = obj->as().getGenerator(); michael@0: JS_ASSERT(gen); michael@0: if (GeneratorHasMarkableFrame(gen)) michael@0: MarkGeneratorFrame(trc, gen); michael@0: } michael@0: michael@0: GeneratorState::GeneratorState(JSContext *cx, JSGenerator *gen, JSGeneratorState futureState) michael@0: : RunState(cx, Generator, gen->fp->script()), michael@0: cx_(cx), michael@0: gen_(gen), michael@0: futureState_(futureState), michael@0: entered_(false) michael@0: { } michael@0: michael@0: GeneratorState::~GeneratorState() michael@0: { michael@0: gen_->fp->setSuspended(); michael@0: michael@0: if (entered_) michael@0: cx_->leaveGenerator(gen_); michael@0: } michael@0: michael@0: InterpreterFrame * michael@0: GeneratorState::pushInterpreterFrame(JSContext *cx) michael@0: { michael@0: /* michael@0: * Write barrier is needed since the generator stack can be updated, michael@0: * and it's not barriered in any other way. We need to do it before michael@0: * gen->state changes, which can cause us to trace the generator michael@0: * differently. michael@0: * michael@0: * We could optimize this by setting a bit on the generator to signify michael@0: * that it has been marked. If this bit has already been set, there is no michael@0: * need to mark again. The bit would have to be reset before the next GC, michael@0: * or else some kind of epoch scheme would have to be used. michael@0: */ michael@0: GeneratorWriteBarrierPre(cx, gen_); michael@0: gen_->state = futureState_; michael@0: michael@0: gen_->fp->clearSuspended(); michael@0: michael@0: cx->enterGenerator(gen_); /* OOM check above. */ michael@0: entered_ = true; michael@0: return gen_->fp; michael@0: } michael@0: michael@0: const Class LegacyGeneratorObject::class_ = { michael@0: "Generator", michael@0: JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS, michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: FinalizeGenerator, michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: TraceGenerator, michael@0: JS_NULL_CLASS_SPEC, michael@0: { michael@0: nullptr, /* outerObject */ michael@0: nullptr, /* innerObject */ michael@0: iterator_iteratorObject, michael@0: } michael@0: }; michael@0: michael@0: const Class StarGeneratorObject::class_ = { michael@0: "Generator", michael@0: JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS, michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: FinalizeGenerator, michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: TraceGenerator, michael@0: JS_NULL_CLASS_SPEC, michael@0: { michael@0: nullptr, /* outerObject */ michael@0: nullptr, /* innerObject */ michael@0: iterator_iteratorObject, michael@0: } michael@0: }; michael@0: michael@0: /* michael@0: * Called from the JSOP_GENERATOR case in the interpreter, with fp referring michael@0: * to the frame by which the generator function was activated. Create a new michael@0: * JSGenerator object, which contains its own InterpreterFrame that we populate michael@0: * from *fp. We know that upon return, the JSOP_GENERATOR opcode will return michael@0: * from the activation in fp, so we can steal away fp->callobj and fp->argsobj michael@0: * if they are non-null. michael@0: */ michael@0: JSObject * michael@0: js_NewGenerator(JSContext *cx, const InterpreterRegs &stackRegs) michael@0: { michael@0: JS_ASSERT(stackRegs.stackDepth() == 0); michael@0: InterpreterFrame *stackfp = stackRegs.fp(); michael@0: michael@0: JS_ASSERT(stackfp->script()->isGenerator()); michael@0: michael@0: Rooted global(cx, &stackfp->global()); michael@0: RootedObject obj(cx); michael@0: if (stackfp->script()->isStarGenerator()) { michael@0: RootedValue pval(cx); michael@0: RootedObject fun(cx, stackfp->fun()); michael@0: // FIXME: This would be faster if we could avoid doing a lookup to get michael@0: // the prototype for the instance. Bug 906600. michael@0: if (!JSObject::getProperty(cx, fun, fun, cx->names().prototype, &pval)) michael@0: return nullptr; michael@0: JSObject *proto = pval.isObject() ? &pval.toObject() : nullptr; michael@0: if (!proto) { michael@0: proto = GlobalObject::getOrCreateStarGeneratorObjectPrototype(cx, global); michael@0: if (!proto) michael@0: return nullptr; michael@0: } michael@0: obj = NewObjectWithGivenProto(cx, &StarGeneratorObject::class_, proto, global); michael@0: } else { michael@0: JS_ASSERT(stackfp->script()->isLegacyGenerator()); michael@0: JSObject *proto = GlobalObject::getOrCreateLegacyGeneratorObjectPrototype(cx, global); michael@0: if (!proto) michael@0: return nullptr; michael@0: obj = NewObjectWithGivenProto(cx, &LegacyGeneratorObject::class_, proto, global); michael@0: } michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: /* Load and compute stack slot counts. */ michael@0: Value *stackvp = stackfp->generatorArgsSnapshotBegin(); michael@0: unsigned vplen = stackfp->generatorArgsSnapshotEnd() - stackvp; michael@0: michael@0: /* Compute JSGenerator size. */ michael@0: unsigned nbytes = sizeof(JSGenerator) + michael@0: (-1 + /* one Value included in JSGenerator */ michael@0: vplen + michael@0: VALUES_PER_STACK_FRAME + michael@0: stackfp->script()->nslots()) * sizeof(HeapValue); michael@0: michael@0: JS_ASSERT(nbytes % sizeof(Value) == 0); michael@0: JS_STATIC_ASSERT(sizeof(InterpreterFrame) % sizeof(HeapValue) == 0); michael@0: michael@0: JSGenerator *gen = (JSGenerator *) cx->calloc_(nbytes); michael@0: if (!gen) michael@0: return nullptr; michael@0: michael@0: /* Cut up floatingStack space. */ michael@0: HeapValue *genvp = gen->stackSnapshot; michael@0: SetValueRangeToUndefined((Value *)genvp, vplen); michael@0: michael@0: InterpreterFrame *genfp = reinterpret_cast(genvp + vplen); michael@0: michael@0: /* Initialize JSGenerator. */ michael@0: gen->obj.init(obj); michael@0: gen->state = JSGEN_NEWBORN; michael@0: gen->fp = genfp; michael@0: gen->prevGenerator = nullptr; michael@0: michael@0: /* Copy from the stack to the generator's floating frame. */ michael@0: gen->regs.rebaseFromTo(stackRegs, *genfp); michael@0: genfp->copyFrameAndValues(cx, (Value *)genvp, stackfp, michael@0: stackvp, stackRegs.sp); michael@0: genfp->setSuspended(); michael@0: obj->setPrivate(gen); michael@0: return obj; michael@0: } michael@0: michael@0: static void michael@0: SetGeneratorClosed(JSContext *cx, JSGenerator *gen); michael@0: michael@0: typedef enum JSGeneratorOp { michael@0: JSGENOP_NEXT, michael@0: JSGENOP_SEND, michael@0: JSGENOP_THROW, michael@0: JSGENOP_CLOSE michael@0: } JSGeneratorOp; michael@0: michael@0: /* michael@0: * Start newborn or restart yielding generator and perform the requested michael@0: * operation inside its frame. michael@0: */ michael@0: static bool michael@0: SendToGenerator(JSContext *cx, JSGeneratorOp op, HandleObject obj, michael@0: JSGenerator *gen, HandleValue arg, GeneratorKind generatorKind, michael@0: MutableHandleValue rval) michael@0: { michael@0: JS_ASSERT(generatorKind == LegacyGenerator || generatorKind == StarGenerator); michael@0: michael@0: if (gen->state == JSGEN_RUNNING || gen->state == JSGEN_CLOSING) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NESTING_GENERATOR); michael@0: return false; michael@0: } michael@0: michael@0: JSGeneratorState futureState; michael@0: JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_OPEN); michael@0: switch (op) { michael@0: case JSGENOP_NEXT: michael@0: case JSGENOP_SEND: michael@0: if (gen->state == JSGEN_OPEN) { michael@0: /* michael@0: * Store the argument to send as the result of the yield michael@0: * expression. The generator stack is not barriered, so we need michael@0: * write barriers here. michael@0: */ michael@0: HeapValue::writeBarrierPre(gen->regs.sp[-1]); michael@0: gen->regs.sp[-1] = arg; michael@0: HeapValue::writeBarrierPost(cx->runtime(), gen->regs.sp[-1], &gen->regs.sp[-1]); michael@0: } michael@0: futureState = JSGEN_RUNNING; michael@0: break; michael@0: michael@0: case JSGENOP_THROW: michael@0: cx->setPendingException(arg); michael@0: futureState = JSGEN_RUNNING; michael@0: break; michael@0: michael@0: default: michael@0: JS_ASSERT(op == JSGENOP_CLOSE); michael@0: JS_ASSERT(generatorKind == LegacyGenerator); michael@0: cx->setPendingException(MagicValue(JS_GENERATOR_CLOSING)); michael@0: futureState = JSGEN_CLOSING; michael@0: break; michael@0: } michael@0: michael@0: bool ok; michael@0: { michael@0: GeneratorState state(cx, gen, futureState); michael@0: ok = RunScript(cx, state); michael@0: if (!ok && gen->state == JSGEN_CLOSED) michael@0: return false; michael@0: } michael@0: michael@0: if (gen->fp->isYielding()) { michael@0: /* michael@0: * Yield is ordinarily infallible, but ok can be false here if a michael@0: * Debugger.Frame.onPop hook fails. michael@0: */ michael@0: JS_ASSERT(gen->state == JSGEN_RUNNING); michael@0: JS_ASSERT(op != JSGENOP_CLOSE); michael@0: gen->fp->clearYielding(); michael@0: gen->state = JSGEN_OPEN; michael@0: GeneratorWriteBarrierPost(cx, gen); michael@0: rval.set(gen->fp->returnValue()); michael@0: return ok; michael@0: } michael@0: michael@0: if (ok) { michael@0: if (generatorKind == StarGenerator) { michael@0: // Star generators return a {value:FOO, done:true} object. michael@0: rval.set(gen->fp->returnValue()); michael@0: } else { michael@0: JS_ASSERT(generatorKind == LegacyGenerator); michael@0: michael@0: // Otherwise we discard the return value and throw a StopIteration michael@0: // if needed. michael@0: rval.setUndefined(); michael@0: if (op != JSGENOP_CLOSE) michael@0: ok = js_ThrowStopIteration(cx); michael@0: } michael@0: } michael@0: michael@0: SetGeneratorClosed(cx, gen); michael@0: return ok; michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: star_generator_next(JSContext *cx, CallArgs args) michael@0: { michael@0: RootedObject thisObj(cx, &args.thisv().toObject()); michael@0: JSGenerator *gen = thisObj->as().getGenerator(); michael@0: michael@0: if (gen->state == JSGEN_CLOSED) { michael@0: RootedObject obj(cx, CreateItrResultObject(cx, JS::UndefinedHandleValue, true)); michael@0: if (!obj) michael@0: return false; michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: if (gen->state == JSGEN_NEWBORN && args.hasDefined(0)) { michael@0: RootedValue val(cx, args[0]); michael@0: js_ReportValueError(cx, JSMSG_BAD_GENERATOR_SEND, michael@0: JSDVG_SEARCH_STACK, val, js::NullPtr()); michael@0: return false; michael@0: } michael@0: michael@0: return SendToGenerator(cx, JSGENOP_SEND, thisObj, gen, args.get(0), StarGenerator, michael@0: args.rval()); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: star_generator_throw(JSContext *cx, CallArgs args) michael@0: { michael@0: RootedObject thisObj(cx, &args.thisv().toObject()); michael@0: michael@0: JSGenerator *gen = thisObj->as().getGenerator(); michael@0: if (gen->state == JSGEN_CLOSED) { michael@0: cx->setPendingException(args.get(0)); michael@0: return false; michael@0: } michael@0: michael@0: return SendToGenerator(cx, JSGENOP_THROW, thisObj, gen, args.get(0), StarGenerator, michael@0: args.rval()); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: legacy_generator_next(JSContext *cx, CallArgs args) michael@0: { michael@0: RootedObject thisObj(cx, &args.thisv().toObject()); michael@0: michael@0: JSGenerator *gen = thisObj->as().getGenerator(); michael@0: if (gen->state == JSGEN_CLOSED) michael@0: return js_ThrowStopIteration(cx); michael@0: michael@0: if (gen->state == JSGEN_NEWBORN && args.hasDefined(0)) { michael@0: RootedValue val(cx, args[0]); michael@0: js_ReportValueError(cx, JSMSG_BAD_GENERATOR_SEND, michael@0: JSDVG_SEARCH_STACK, val, js::NullPtr()); michael@0: return false; michael@0: } michael@0: michael@0: return SendToGenerator(cx, JSGENOP_SEND, thisObj, gen, args.get(0), LegacyGenerator, michael@0: args.rval()); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: legacy_generator_throw(JSContext *cx, CallArgs args) michael@0: { michael@0: RootedObject thisObj(cx, &args.thisv().toObject()); michael@0: michael@0: JSGenerator *gen = thisObj->as().getGenerator(); michael@0: if (gen->state == JSGEN_CLOSED) { michael@0: cx->setPendingException(args.length() >= 1 ? args[0] : UndefinedValue()); michael@0: return false; michael@0: } michael@0: michael@0: return SendToGenerator(cx, JSGENOP_THROW, thisObj, gen, args.get(0), LegacyGenerator, michael@0: args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: CloseLegacyGenerator(JSContext *cx, HandleObject obj, MutableHandleValue rval) michael@0: { michael@0: JS_ASSERT(obj->is()); michael@0: michael@0: JSGenerator *gen = obj->as().getGenerator(); michael@0: michael@0: if (gen->state == JSGEN_CLOSED) { michael@0: rval.setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: if (gen->state == JSGEN_NEWBORN) { michael@0: SetGeneratorClosed(cx, gen); michael@0: rval.setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: return SendToGenerator(cx, JSGENOP_CLOSE, obj, gen, JS::UndefinedHandleValue, LegacyGenerator, michael@0: rval); michael@0: } michael@0: michael@0: static bool michael@0: CloseLegacyGenerator(JSContext *cx, HandleObject obj) michael@0: { michael@0: RootedValue rval(cx); michael@0: return CloseLegacyGenerator(cx, obj, &rval); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: legacy_generator_close(JSContext *cx, CallArgs args) michael@0: { michael@0: RootedObject thisObj(cx, &args.thisv().toObject()); michael@0: michael@0: return CloseLegacyGenerator(cx, thisObj, args.rval()); michael@0: } michael@0: michael@0: template michael@0: MOZ_ALWAYS_INLINE bool michael@0: IsObjectOfType(HandleValue v) michael@0: { michael@0: return v.isObject() && v.toObject().is(); michael@0: } michael@0: michael@0: template michael@0: static bool michael@0: NativeMethod(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod, Impl>(cx, args); michael@0: } michael@0: michael@0: #define JSPROP_ROPERM (JSPROP_READONLY | JSPROP_PERMANENT) michael@0: #define JS_METHOD(name, T, impl, len, attrs) JS_FN(name, (NativeMethod), len, attrs) michael@0: michael@0: static const JSFunctionSpec star_generator_methods[] = { michael@0: JS_SELF_HOSTED_FN("@@iterator", "IteratorIdentity", 0, 0), michael@0: JS_METHOD("next", StarGeneratorObject, star_generator_next, 1, 0), michael@0: JS_METHOD("throw", StarGeneratorObject, star_generator_throw, 1, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: static const JSFunctionSpec legacy_generator_methods[] = { michael@0: JS_SELF_HOSTED_FN("@@iterator", "LegacyGeneratorIteratorShim", 0, 0), michael@0: // "send" is an alias for "next". michael@0: JS_METHOD("next", LegacyGeneratorObject, legacy_generator_next, 1, JSPROP_ROPERM), michael@0: JS_METHOD("send", LegacyGeneratorObject, legacy_generator_next, 1, JSPROP_ROPERM), michael@0: JS_METHOD("throw", LegacyGeneratorObject, legacy_generator_throw, 1, JSPROP_ROPERM), michael@0: JS_METHOD("close", LegacyGeneratorObject, legacy_generator_close, 0, JSPROP_ROPERM), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: static JSObject* michael@0: NewSingletonObjectWithObjectPrototype(JSContext *cx, Handle global) michael@0: { michael@0: JSObject *proto = global->getOrCreateObjectPrototype(cx); michael@0: if (!proto) michael@0: return nullptr; michael@0: return NewObjectWithGivenProto(cx, &JSObject::class_, proto, global, SingletonObject); michael@0: } michael@0: michael@0: static JSObject* michael@0: NewSingletonObjectWithFunctionPrototype(JSContext *cx, Handle global) michael@0: { michael@0: JSObject *proto = global->getOrCreateFunctionPrototype(cx); michael@0: if (!proto) michael@0: return nullptr; michael@0: return NewObjectWithGivenProto(cx, &JSObject::class_, proto, global, SingletonObject); michael@0: } michael@0: michael@0: /* static */ bool michael@0: GlobalObject::initIteratorClasses(JSContext *cx, Handle global) michael@0: { michael@0: RootedObject iteratorProto(cx); michael@0: Value iteratorProtoVal = global->getPrototype(JSProto_Iterator); michael@0: if (iteratorProtoVal.isObject()) { michael@0: iteratorProto = &iteratorProtoVal.toObject(); michael@0: } else { michael@0: iteratorProto = global->createBlankPrototype(cx, &PropertyIteratorObject::class_); michael@0: if (!iteratorProto) michael@0: return false; michael@0: michael@0: AutoIdVector blank(cx); michael@0: NativeIterator *ni = NativeIterator::allocateIterator(cx, 0, blank); michael@0: if (!ni) michael@0: return false; michael@0: ni->init(nullptr, nullptr, 0 /* flags */, 0, 0); michael@0: michael@0: iteratorProto->as().setNativeIterator(ni); michael@0: michael@0: Rooted ctor(cx); michael@0: ctor = global->createConstructor(cx, IteratorConstructor, cx->names().Iterator, 2); michael@0: if (!ctor) michael@0: return false; michael@0: if (!LinkConstructorAndPrototype(cx, ctor, iteratorProto)) michael@0: return false; michael@0: if (!DefinePropertiesAndBrand(cx, iteratorProto, nullptr, iterator_methods)) michael@0: return false; michael@0: if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_Iterator, michael@0: ctor, iteratorProto)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: RootedObject proto(cx); michael@0: if (global->getSlot(ARRAY_ITERATOR_PROTO).isUndefined()) { michael@0: const Class *cls = &ArrayIteratorObject::class_; michael@0: proto = global->createBlankPrototypeInheriting(cx, cls, *iteratorProto); michael@0: if (!proto || !DefinePropertiesAndBrand(cx, proto, nullptr, array_iterator_methods)) michael@0: return false; michael@0: global->setReservedSlot(ARRAY_ITERATOR_PROTO, ObjectValue(*proto)); michael@0: } michael@0: michael@0: if (global->getSlot(STRING_ITERATOR_PROTO).isUndefined()) { michael@0: const Class *cls = &StringIteratorPrototypeClass; michael@0: proto = global->createBlankPrototype(cx, cls); michael@0: if (!proto || !DefinePropertiesAndBrand(cx, proto, nullptr, string_iterator_methods)) michael@0: return false; michael@0: global->setReservedSlot(STRING_ITERATOR_PROTO, ObjectValue(*proto)); michael@0: } michael@0: michael@0: if (global->getSlot(LEGACY_GENERATOR_OBJECT_PROTO).isUndefined()) { michael@0: proto = NewSingletonObjectWithObjectPrototype(cx, global); michael@0: if (!proto || !DefinePropertiesAndBrand(cx, proto, nullptr, legacy_generator_methods)) michael@0: return false; michael@0: global->setReservedSlot(LEGACY_GENERATOR_OBJECT_PROTO, ObjectValue(*proto)); michael@0: } michael@0: michael@0: if (global->getSlot(STAR_GENERATOR_OBJECT_PROTO).isUndefined()) { michael@0: RootedObject genObjectProto(cx, NewSingletonObjectWithObjectPrototype(cx, global)); michael@0: if (!genObjectProto) michael@0: return false; michael@0: if (!DefinePropertiesAndBrand(cx, genObjectProto, nullptr, star_generator_methods)) michael@0: return false; michael@0: michael@0: RootedObject genFunctionProto(cx, NewSingletonObjectWithFunctionPrototype(cx, global)); michael@0: if (!genFunctionProto) michael@0: return false; michael@0: if (!LinkConstructorAndPrototype(cx, genFunctionProto, genObjectProto)) michael@0: return false; michael@0: michael@0: RootedValue function(cx, global->getConstructor(JSProto_Function)); michael@0: if (!function.toObjectOrNull()) michael@0: return false; michael@0: RootedAtom name(cx, cx->names().GeneratorFunction); michael@0: RootedObject genFunction(cx, NewFunctionWithProto(cx, NullPtr(), Generator, 1, michael@0: JSFunction::NATIVE_CTOR, global, name, michael@0: &function.toObject())); michael@0: if (!genFunction) michael@0: return false; michael@0: if (!LinkConstructorAndPrototype(cx, genFunction, genFunctionProto)) michael@0: return false; michael@0: michael@0: global->setSlot(STAR_GENERATOR_OBJECT_PROTO, ObjectValue(*genObjectProto)); michael@0: global->setConstructor(JSProto_GeneratorFunction, ObjectValue(*genFunction)); michael@0: global->setPrototype(JSProto_GeneratorFunction, ObjectValue(*genFunctionProto)); michael@0: } michael@0: michael@0: if (global->getPrototype(JSProto_StopIteration).isUndefined()) { michael@0: proto = global->createBlankPrototype(cx, &StopIterationObject::class_); michael@0: if (!proto || !JSObject::freeze(cx, proto)) michael@0: return false; michael@0: michael@0: // This should use a non-JSProtoKey'd slot, but this is easier for now. michael@0: if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_StopIteration, proto, proto)) michael@0: return false; michael@0: michael@0: global->setConstructor(JSProto_StopIteration, ObjectValue(*proto)); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: JSObject * michael@0: js_InitIteratorClasses(JSContext *cx, HandleObject obj) michael@0: { michael@0: Rooted global(cx, &obj->as()); michael@0: if (!GlobalObject::initIteratorClasses(cx, global)) michael@0: return nullptr; michael@0: return global->getIteratorPrototype(); michael@0: }