diff -r 000000000000 -r 6474c204b198 js/src/jsiter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/src/jsiter.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,2095 @@ +/* -*- 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 "jsscriptinlines.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)); + + // StopIteration isn't a constructor, but it's stored in GlobalObject + // as one, out of laziness. Hence the GetBuiltinConstructor call here. + RootedObject ctor(cx); + if (GetBuiltinConstructor(cx, JSProto_StopIteration, &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_SWEPT_FRAME_PATTERN, sizeof(InterpreterFrame)); + JS_POISON(gen, JS_SWEPT_FRAME_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_); +} + +InterpreterFrame * +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 InterpreterFrame 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); + InterpreterFrame *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(InterpreterFrame) % 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); + + InterpreterFrame *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(); +}