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