blob: 9b27317683bec3e2107dcf8708788f683cabe0e9 [file] [log] [blame]
/* -*- 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/Maybe.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 "jsscript.h"
#include "jstypes.h"
#include "jsutil.h"
#include "ds/Sort.h"
#include "gc/Marking.h"
#include "js/Proxy.h"
#include "vm/GeneratorObject.h"
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
#include "vm/Shape.h"
#include "vm/StopIterationObject.h"
#include "vm/TypedArrayCommon.h"
#include "jsscriptinlines.h"
#include "vm/NativeObject-inl.h"
#include "vm/Stack-inl.h"
#include "vm/String-inl.h"
using namespace js;
using namespace js::gc;
using JS::ForOfIterator;
using mozilla::ArrayLength;
using mozilla::Maybe;
using mozilla::PodCopy;
using mozilla::PodZero;
typedef Rooted<PropertyIteratorObject*> RootedPropertyIteratorObject;
static const gc::AllocKind ITERATOR_FINALIZE_KIND = gc::AllocKind::OBJECT2_BACKGROUND;
void
NativeIterator::mark(JSTracer* trc)
{
for (HeapPtrFlatString* str = begin(); str < end(); str++)
TraceEdge(trc, str, "prop");
if (obj)
TraceEdge(trc, &obj, "obj");
for (size_t i = 0; i < guard_length; i++)
guard_array[i].trace(trc);
// 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_)
TraceManuallyBarrieredEdge(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<jsid, IdHashPolicy> 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, Maybe<IdSet>& ht, AutoIdVector* props)
{
// Allow duplicate properties from Proxy's [[OwnPropertyKeys]].
bool proxyOwnProperty = pobj->is<ProxyObject>() && (flags & JSITER_OWNONLY);
if (!proxyOwnProperty && (!(flags & JSITER_OWNONLY) || pobj->is<ProxyObject>() ||
pobj->getOps()->enumerate))
{
if (!ht) {
ht.emplace(cx);
// Most of the time there are only a handful of entries.
if (!ht->init(5))
return false;
}
// 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<ProxyObject>() || pobj->getProto() || pobj->getOps()->enumerate) && !ht->add(p, id))
return false;
}
// Symbol-keyed properties and nonenumerable properties are skipped unless
// the caller specifically asks for them. A caller can also filter out
// non-symbols by asking for JSITER_SYMBOLSONLY.
if (JSID_IS_SYMBOL(id) ? !(flags & JSITER_SYMBOLS) : (flags & JSITER_SYMBOLSONLY))
return true;
if (!enumerable && !(flags & JSITER_HIDDEN))
return true;
return props->append(id);
}
static bool
EnumerateExtraProperties(JSContext* cx, HandleObject obj, unsigned flags, Maybe<IdSet>& ht,
AutoIdVector* props)
{
MOZ_ASSERT(obj->getOps()->enumerate);
AutoIdVector properties(cx);
bool enumerableOnly = !(flags & JSITER_HIDDEN);
if (!obj->getOps()->enumerate(cx, obj, properties, enumerableOnly))
return false;
RootedId id(cx);
for (size_t n = 0; n < properties.length(); n++) {
id = properties[n];
// The enumerate hook does not indicate whether the properties
// it returns are enumerable or not. Since we already passed
// `enumerableOnly` to the hook to filter out non-enumerable
// properties, it doesn't really matter what we pass here.
bool enumerable = true;
if (!Enumerate(cx, obj, id, enumerable, flags, ht, props))
return false;
}
return true;
}
static bool
SortComparatorIntegerIds(jsid a, jsid b, bool* lessOrEqualp)
{
uint32_t indexA, indexB;
MOZ_ALWAYS_TRUE(IdIsIndex(a, &indexA));
MOZ_ALWAYS_TRUE(IdIsIndex(b, &indexB));
*lessOrEqualp = (indexA <= indexB);
return true;
}
static bool
EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags, Maybe<IdSet>& ht,
AutoIdVector* props, Handle<UnboxedPlainObject*> unboxed = nullptr)
{
bool enumerateSymbols;
if (flags & JSITER_SYMBOLSONLY) {
enumerateSymbols = true;
} else {
/* Collect any dense elements from this object. */
size_t firstElemIndex = props->length();
size_t initlen = pobj->getDenseInitializedLength();
const Value* vp = pobj->getDenseElements();
bool hasHoles = false;
for (size_t i = 0; i < initlen; ++i, ++vp) {
if (vp->isMagic(JS_ELEMENTS_HOLE)) {
hasHoles = true;
} else {
/* 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 or shared typed array elements from this object. */
if (IsAnyTypedArray(pobj)) {
size_t len = AnyTypedArrayLength(pobj);
for (size_t i = 0; i < len; i++) {
if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props))
return false;
}
}
// Collect any sparse elements from this object.
bool isIndexed = pobj->isIndexed();
if (isIndexed) {
// If the dense elements didn't have holes, we don't need to include
// them in the sort.
if (!hasHoles)
firstElemIndex = props->length();
for (Shape::Range<NoGC> r(pobj->lastProperty()); !r.empty(); r.popFront()) {
Shape& shape = r.front();
jsid id = shape.propid();
uint32_t dummy;
if (IdIsIndex(id, &dummy)) {
if (!Enumerate(cx, pobj, id, shape.enumerable(), flags, ht, props))
return false;
}
}
MOZ_ASSERT(firstElemIndex <= props->length());
jsid* ids = props->begin() + firstElemIndex;
size_t n = props->length() - firstElemIndex;
AutoIdVector tmp(cx);
if (!tmp.resize(n))
return false;
PodCopy(tmp.begin(), ids, n);
if (!MergeSort(ids, n, tmp.begin(), SortComparatorIntegerIds))
return false;
}
if (unboxed) {
// If |unboxed| is set then |pobj| is the expando for an unboxed
// plain object we are enumerating. Add the unboxed properties
// themselves here since they are all property names that were
// given to the object before any of the expando's properties.
MOZ_ASSERT(pobj->is<UnboxedExpandoObject>());
if (!EnumerateExtraProperties(cx, unboxed, flags, ht, props))
return false;
}
size_t initialLength = props->length();
/* Collect all unique property names from this object's shape. */
bool symbolsFound = false;
Shape::Range<NoGC> r(pobj->lastProperty());
for (; !r.empty(); r.popFront()) {
Shape& shape = r.front();
jsid id = shape.propid();
if (JSID_IS_SYMBOL(id)) {
symbolsFound = true;
continue;
}
uint32_t dummy;
if (isIndexed && IdIsIndex(id, &dummy))
continue;
if (!Enumerate(cx, pobj, id, shape.enumerable(), flags, ht, props))
return false;
}
::Reverse(props->begin() + initialLength, props->end());
enumerateSymbols = symbolsFound && (flags & JSITER_SYMBOLS);
}
if (enumerateSymbols) {
// Do a second pass to collect symbols. ES6 draft rev 25 (2014 May 22)
// 9.1.12 requires that all symbols appear after all strings in the
// result.
size_t initialLength = props->length();
for (Shape::Range<NoGC> r(pobj->lastProperty()); !r.empty(); r.popFront()) {
Shape& shape = r.front();
jsid id = shape.propid();
if (JSID_IS_SYMBOL(id)) {
if (!Enumerate(cx, pobj, id, 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 order on jsids that is as stable as possible
// across executions.
if (a == b) {
*lessOrEqualp = true;
return true;
}
size_t ta = JSID_BITS(a) & JSID_TYPE_MASK;
size_t tb = JSID_BITS(b) & JSID_TYPE_MASK;
if (ta != tb) {
*lessOrEqualp = (ta <= tb);
return true;
}
if (JSID_IS_INT(a)) {
*lessOrEqualp = (JSID_TO_INT(a) <= JSID_TO_INT(b));
return true;
}
RootedString astr(cx), bstr(cx);
if (JSID_IS_SYMBOL(a)) {
MOZ_ASSERT(JSID_IS_SYMBOL(b));
JS::SymbolCode ca = JSID_TO_SYMBOL(a)->code();
JS::SymbolCode cb = JSID_TO_SYMBOL(b)->code();
if (ca != cb) {
*lessOrEqualp = uint32_t(ca) <= uint32_t(cb);
return true;
}
MOZ_ASSERT(ca == JS::SymbolCode::InSymbolRegistry || ca == JS::SymbolCode::UniqueSymbol);
astr = JSID_TO_SYMBOL(a)->description();
bstr = JSID_TO_SYMBOL(b)->description();
if (!astr || !bstr) {
*lessOrEqualp = !astr;
return true;
}
// Fall through to string comparison on the descriptions. The sort
// order is nondeterministic if two different unique symbols have
// the same description.
} else {
astr = IdToString(cx, a);
if (!astr)
return false;
bstr = 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, HandleObject pobj_, unsigned flags, AutoIdVector* props)
{
// We initialize |ht| lazily (in Enumerate()) because it ends up unused
// anywhere from 67--99.9% of the time.
Maybe<IdSet> ht;
RootedObject pobj(cx, pobj_);
do {
if (pobj->getOps()->enumerate) {
if (pobj->is<UnboxedPlainObject>() && pobj->as<UnboxedPlainObject>().maybeExpando()) {
// Special case unboxed objects with an expando object.
RootedNativeObject expando(cx, pobj->as<UnboxedPlainObject>().maybeExpando());
if (!EnumerateNativeProperties(cx, expando, flags, ht, props,
pobj.as<UnboxedPlainObject>()))
{
return false;
}
} else {
if (!EnumerateExtraProperties(cx, pobj, flags, ht, props))
return false;
if (pobj->isNative()) {
if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, ht, props))
return false;
}
}
} else if (pobj->isNative()) {
// Give the object a chance to resolve all lazy properties
if (JSEnumerateOp enumerate = pobj->getClass()->enumerate) {
if (!enumerate(cx, pobj.as<NativeObject>()))
return false;
}
if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, ht, props))
return false;
} else if (pobj->is<ProxyObject>()) {
AutoIdVector proxyProps(cx);
if (flags & JSITER_HIDDEN || flags & JSITER_SYMBOLS) {
// This gets all property keys, both strings and
// symbols. The call to Enumerate in the loop below
// will filter out unwanted keys, per the flags.
if (!Proxy::ownPropertyKeys(cx, pobj, proxyProps))
return false;
Rooted<PropertyDescriptor> desc(cx);
for (size_t n = 0, len = proxyProps.length(); n < len; n++) {
bool enumerable = false;
// We need to filter, if the caller just wants enumerable
// symbols.
if (!(flags & JSITER_HIDDEN)) {
if (!Proxy::getOwnPropertyDescriptor(cx, pobj, proxyProps[n], &desc))
return false;
enumerable = desc.enumerable();
}
if (!Enumerate(cx, pobj, proxyProps[n], enumerable, flags, ht, props))
return false;
}
} else {
// Returns enumerable property names (no symbols).
if (!Proxy::getOwnEnumerablePropertyKeys(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;
}
}
} else {
MOZ_CRASH("non-native objects must have an enumerate op");
}
if (flags & JSITER_OWNONLY)
break;
if (!GetPrototype(cx, pobj, &pobj))
return false;
} while (pobj != 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;
}
JS_FRIEND_API(bool)
js::GetPropertyKeys(JSContext* cx, HandleObject obj, unsigned flags, AutoIdVector* props)
{
return Snapshot(cx, obj,
flags & (JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | JSITER_SYMBOLSONLY),
props);
}
size_t sCustomIteratorCount = 0;
static inline bool
GetCustomIterator(JSContext* cx, HandleObject obj, unsigned flags, MutableHandleObject objp)
{
JS_CHECK_RECURSION(cx, return false);
RootedValue rval(cx);
/* Check whether we have a valid __iterator__ method. */
HandlePropertyName name = cx->names().iteratorIntrinsic;
if (!GetProperty(cx, obj, obj, name, &rval))
return false;
/* If there is no custom __iterator__ method, we are done here. */
if (!rval.isObject()) {
objp.set(nullptr);
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), rval, 1, &arg, &rval))
return false;
if (rval.isPrimitive()) {
// Ignore the stack when throwing. We can't tell whether we were
// supposed to skip over a new.target or not.
JSAutoByteString bytes;
if (!AtomToPrintableString(cx, name, &bytes))
return false;
RootedValue val(cx, ObjectValue(*obj));
ReportValueError2(cx, JSMSG_BAD_TRAP_RETURN_VALUE,
JSDVG_IGNORE_STACK, val, nullptr, bytes.ptr());
return false;
}
objp.set(&rval.toObject());
return true;
}
template <typename T>
static inline bool
Compare(T* a, T* b, size_t c)
{
size_t n = (c + size_t(7)) / size_t(8);
switch (c % 8) {
case 0: do { if (*a++ != *b++) return false;
case 7: if (*a++ != *b++) return false;
case 6: if (*a++ != *b++) return false;
case 5: if (*a++ != *b++) return false;
case 4: if (*a++ != *b++) return false;
case 3: if (*a++ != *b++) return false;
case 2: if (*a++ != *b++) return false;
case 1: if (*a++ != *b++) return false;
} while (--n > 0);
}
return true;
}
static bool legacy_iterator_next(JSContext* cx, unsigned argc, Value* vp);
static inline PropertyIteratorObject*
NewPropertyIteratorObject(JSContext* cx, unsigned flags)
{
if (flags & JSITER_ENUMERATE) {
RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &PropertyIteratorObject::class_,
TaggedProto(nullptr)));
if (!group)
return nullptr;
const Class* clasp = &PropertyIteratorObject::class_;
RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(nullptr),
ITERATOR_FINALIZE_KIND));
if (!shape)
return nullptr;
JSObject* obj = JSObject::create(cx, ITERATOR_FINALIZE_KIND,
GetInitialHeap(GenericObject, clasp), shape, group);
if (!obj)
return nullptr;
PropertyIteratorObject* res = &obj->as<PropertyIteratorObject>();
MOZ_ASSERT(res->numFixedSlots() == JSObject::ITER_CLASS_NFIXED_SLOTS);
return res;
}
Rooted<PropertyIteratorObject*> res(cx, NewBuiltinClassInstance<PropertyIteratorObject>(cx));
if (!res)
return nullptr;
if (flags == 0) {
// Redefine next as an own property. This ensure that deleting the
// next method on the prototype doesn't break cross-global for .. in.
// We don't have to do this for JSITER_ENUMERATE because that object always
// takes an optimized path.
RootedFunction next(cx, NewNativeFunction(cx, legacy_iterator_next, 0,
HandlePropertyName(cx->names().next)));
if (!next)
return nullptr;
RootedValue value(cx, ObjectValue(*next));
if (!DefineProperty(cx, res, cx->names().next, value))
return nullptr;
}
return res;
}
NativeIterator*
NativeIterator::allocateIterator(JSContext* cx, uint32_t numGuards, const AutoIdVector& props)
{
JS_STATIC_ASSERT(sizeof(ReceiverGuard) == 2 * sizeof(void*));
size_t plength = props.length();
NativeIterator* ni = cx->zone()->pod_malloc_with_extra<NativeIterator, void*>(plength + numGuards * 2);
if (!ni) {
ReportOutOfMemory(cx);
return nullptr;
}
AutoValueVector strings(cx);
ni->props_array = ni->props_cursor = reinterpret_cast<HeapPtrFlatString*>(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* maybecx)
{
NativeIterator* ni = js_pod_malloc<NativeIterator>();
if (!ni) {
if (maybecx)
ReportOutOfMemory(maybecx);
return nullptr;
}
PodZero(ni);
ni->next_ = ni;
ni->prev_ = ni;
return ni;
}
inline void
NativeIterator::init(JSObject* obj, JSObject* iterObj, unsigned flags, uint32_t numGuards, uint32_t key)
{
this->obj.init(obj);
this->iterObj_ = iterObj;
this->flags = flags;
this->guard_array = (HeapReceiverGuard*) this->props_end;
this->guard_length = numGuards;
this->guard_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);
MOZ_ASSERT(!(ni->flags & JSITER_ACTIVE));
ni->flags |= JSITER_ACTIVE;
}
}
static inline bool
VectorToKeyIterator(JSContext* cx, HandleObject obj, unsigned flags, AutoIdVector& keys,
uint32_t numGuards, uint32_t key, MutableHandleObject objp)
{
MOZ_ASSERT(!(flags & JSITER_FOREACH));
if (obj->isSingleton() && !obj->setIteratedSingleton(cx))
return false;
MarkObjectGroupFlags(cx, obj, OBJECT_FLAG_ITERATED);
Rooted<PropertyIteratorObject*> iterobj(cx, NewPropertyIteratorObject(cx, flags));
if (!iterobj)
return false;
NativeIterator* ni = NativeIterator::allocateIterator(cx, numGuards, keys);
if (!ni)
return false;
ni->init(obj, iterobj, flags, numGuards, key);
if (numGuards) {
// Fill in the guard array from scratch.
JSObject* pobj = obj;
size_t ind = 0;
do {
ni->guard_array[ind++].init(ReceiverGuard(pobj));
pobj = pobj->getProto();
} while (pobj);
MOZ_ASSERT(ind == numGuards);
}
iterobj->setNativeIterator(ni);
objp.set(iterobj);
RegisterEnumerator(cx, iterobj, ni);
return true;
}
static bool
VectorToValueIterator(JSContext* cx, HandleObject obj, unsigned flags, AutoIdVector& keys,
MutableHandleObject objp)
{
MOZ_ASSERT(flags & JSITER_FOREACH);
if (obj->isSingleton() && !obj->setIteratedSingleton(cx))
return false;
MarkObjectGroupFlags(cx, obj, OBJECT_FLAG_ITERATED);
Rooted<PropertyIteratorObject*> 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);
objp.set(iterobj);
RegisterEnumerator(cx, iterobj, ni);
return true;
}
bool
js::EnumeratedIdVectorToIterator(JSContext* cx, HandleObject obj, unsigned flags,
AutoIdVector& props, MutableHandleObject objp)
{
if (!(flags & JSITER_FOREACH))
return VectorToKeyIterator(cx, obj, flags, props, 0, 0, objp);
return VectorToValueIterator(cx, obj, flags, props, objp);
}
// Mainly used for .. in over null/undefined
bool
js::NewEmptyPropertyIterator(JSContext* cx, unsigned flags, MutableHandleObject objp)
{
Rooted<PropertyIteratorObject*> iterobj(cx, NewPropertyIteratorObject(cx, flags));
if (!iterobj)
return false;
AutoIdVector keys(cx); // Empty
NativeIterator* ni = NativeIterator::allocateIterator(cx, 0, keys);
if (!ni)
return false;
ni->init(nullptr, iterobj, flags, 0, 0);
iterobj->setNativeIterator(ni);
objp.set(iterobj);
RegisterEnumerator(cx, iterobj, ni);
return true;
}
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;
}
static inline bool
CanCompareIterableObjectToCache(JSObject* obj)
{
if (obj->isNative())
return obj->as<NativeObject>().hasEmptyElements();
if (obj->is<UnboxedPlainObject>()) {
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
return expando->hasEmptyElements();
return true;
}
return false;
}
static inline bool
CanCacheIterableObject(JSContext* cx, JSObject* obj)
{
if (!CanCompareIterableObjectToCache(obj))
return false;
if (obj->isNative()) {
if (IsAnyTypedArray(obj) ||
obj->hasUncacheableProto() ||
obj->getOps()->enumerate ||
obj->getClass()->enumerate ||
obj->as<NativeObject>().containsPure(cx->names().iteratorIntrinsic))
{
return false;
}
}
return true;
}
bool
js::GetIterator(JSContext* cx, HandleObject obj, unsigned flags, MutableHandleObject objp)
{
if (obj->is<PropertyIteratorObject>() || obj->is<LegacyGeneratorObject>()) {
objp.set(obj);
return true;
}
// We should only call the enumerate trap for "for-in".
// Or when we call GetIterator from the Proxy [[Enumerate]] hook.
// In the future also for Reflect.enumerate.
// JSITER_ENUMERATE is just an optimization and the same
// as flags == 0 otherwise.
if (flags == 0 || flags == JSITER_ENUMERATE) {
if (obj->is<ProxyObject>())
return Proxy::enumerate(cx, obj, objp);
}
Vector<ReceiverGuard, 8> guards(cx);
uint32_t key = 0;
if (flags == JSITER_ENUMERATE) {
// Check to see if this is the same as the most recent object which was
// iterated over.
PropertyIteratorObject* last = cx->runtime()->nativeIterCache.last;
if (last) {
NativeIterator* lastni = last->getNativeIterator();
if (!(lastni->flags & (JSITER_ACTIVE|JSITER_UNREUSABLE)) &&
CanCompareIterableObjectToCache(obj) &&
ReceiverGuard(obj) == lastni->guard_array[0])
{
JSObject* proto = obj->getProto();
if (CanCompareIterableObjectToCache(proto) &&
ReceiverGuard(proto) == lastni->guard_array[1] &&
!proto->getProto())
{
objp.set(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 (!CanCacheIterableObject(cx, pobj)) {
guards.clear();
goto miss;
}
ReceiverGuard guard(pobj);
key = (key + (key << 16)) ^ guard.hash();
if (!guards.append(guard))
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->guard_key == key &&
ni->guard_length == guards.length() &&
Compare(reinterpret_cast<ReceiverGuard*>(ni->guard_array),
guards.begin(), ni->guard_length))
{
objp.set(iterobj);
UpdateNativeIterator(ni, obj);
RegisterEnumerator(cx, iterobj, ni);
if (guards.length() == 2)
cx->runtime()->nativeIterCache.last = iterobj;
return true;
}
}
}
miss:
if (!GetCustomIterator(cx, obj, flags, objp))
return false;
if (objp)
return true;
AutoIdVector keys(cx);
if (flags & JSITER_FOREACH) {
MOZ_ASSERT(guards.empty());
if (!Snapshot(cx, obj, flags, &keys))
return false;
if (!VectorToValueIterator(cx, obj, flags, keys, objp))
return false;
} else {
if (!Snapshot(cx, obj, flags, &keys))
return false;
if (!VectorToKeyIterator(cx, obj, flags, keys, guards.length(), key, objp))
return false;
}
PropertyIteratorObject* iterobj = &objp->as<PropertyIteratorObject>();
/* Cache the iterator object if possible. */
if (guards.length())
cx->runtime()->nativeIterCache.set(key, iterobj);
if (guards.length() == 2)
cx->runtime()->nativeIterCache.last = iterobj;
return true;
}
JSObject*
js::GetIteratorObject(JSContext* cx, HandleObject obj, uint32_t flags)
{
RootedObject iterator(cx);
if (!GetIterator(cx, obj, flags, &iterator))
return nullptr;
return iterator;
}
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;
RootedPlainObject obj(cx, NewObjectWithGivenProto<PlainObject>(cx, proto));
if (!obj)
return nullptr;
if (!DefineProperty(cx, obj, cx->names().value, value))
return nullptr;
RootedValue doneBool(cx, BooleanValue(done));
if (!DefineProperty(cx, obj, cx->names().done, doneBool))
return nullptr;
return obj;
}
bool
js::ThrowStopIteration(JSContext* cx)
{
MOZ_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) {
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
NativeIteratorNext(JSContext* cx, NativeIterator* ni, MutableHandleValue rval, bool* done)
{
*done = false;
if (ni->props_cursor >= ni->props_end) {
*done = true;
return true;
}
if (MOZ_LIKELY(ni->isKeyIter())) {
rval.setString(*ni->current());
ni->incCursor();
return true;
}
// Non-standard Iterator for "for each"
RootedId id(cx);
RootedValue current(cx, StringValue(*ni->current()));
if (!ValueToId<CanGC>(cx, current, &id))
return false;
ni->incCursor();
RootedObject obj(cx, ni->obj);
if (!GetProperty(cx, obj, obj, id, rval))
return false;
// JS 1.7 only: for each (let [k, v] in obj)
if (ni->flags & JSITER_KEYVALUE)
return NewKeyValuePair(cx, id, rval, rval);
return true;
}
MOZ_ALWAYS_INLINE bool
IsIterator(HandleValue v)
{
return v.isObject() && v.toObject().hasClass(&PropertyIteratorObject::class_);
}
MOZ_ALWAYS_INLINE bool
legacy_iterator_next_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsIterator(args.thisv()));
RootedObject thisObj(cx, &args.thisv().toObject());
NativeIterator* ni = thisObj.as<PropertyIteratorObject>()->getNativeIterator();
RootedValue value(cx);
bool done;
if (!NativeIteratorNext(cx, ni, &value, &done))
return false;
// Use old iterator protocol for compatibility reasons.
if (done) {
ThrowStopIteration(cx);
return false;
}
args.rval().set(value);
return true;
}
static bool
legacy_iterator_next(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsIterator, legacy_iterator_next_impl>(cx, args);
}
static const JSFunctionSpec legacy_iterator_methods[] = {
JS_SELF_HOSTED_SYM_FN(iterator, "LegacyIteratorShim", 0, 0),
JS_FN("next", legacy_iterator_next, 0, 0),
JS_FS_END
};
size_t
PropertyIteratorObject::sizeOfMisc(mozilla::MallocSizeOf mallocSizeOf) const
{
return mallocSizeOf(getPrivate());
}
void
PropertyIteratorObject::trace(JSTracer* trc, JSObject* obj)
{
if (NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator())
ni->mark(trc);
}
void
PropertyIteratorObject::finalize(FreeOp* fop, JSObject* obj)
{
if (NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator())
fop->free_(ni);
}
const Class PropertyIteratorObject::class_ = {
"Iterator",
JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator) |
JSCLASS_HAS_PRIVATE |
JSCLASS_BACKGROUND_FINALIZE,
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
finalize,
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
trace
};
static const Class ArrayIteratorPrototypeClass = {
"Array Iterator",
0
};
enum {
ArrayIteratorSlotIteratedObject,
ArrayIteratorSlotNextIndex,
ArrayIteratorSlotItemKind,
ArrayIteratorSlotCount
};
const Class ArrayIteratorObject::class_ = {
"Array Iterator",
JSCLASS_HAS_RESERVED_SLOTS(ArrayIteratorSlotCount)
};
static const JSFunctionSpec array_iterator_methods[] = {
JS_SELF_HOSTED_FN("next", "ArrayIteratorNext", 0, 0),
JS_FS_END
};
static const Class StringIteratorPrototypeClass = {
"String Iterator",
0
};
enum {
StringIteratorSlotIteratedObject,
StringIteratorSlotNextIndex,
StringIteratorSlotCount
};
const Class StringIteratorObject::class_ = {
"String Iterator",
JSCLASS_HAS_RESERVED_SLOTS(StringIteratorSlotCount)
};
static const JSFunctionSpec string_iterator_methods[] = {
JS_SELF_HOSTED_FN("next", "StringIteratorNext", 0, 0),
JS_FS_END
};
enum {
ListIteratorSlotIteratedObject,
ListIteratorSlotNextIndex,
ListIteratorSlotNextMethod,
ListIteratorSlotCount
};
const Class ListIteratorObject::class_ = {
"List Iterator",
JSCLASS_HAS_RESERVED_SLOTS(ListIteratorSlotCount)
};
bool
js::ValueToIterator(JSContext* cx, unsigned flags, MutableHandleValue vp)
{
/* JSITER_KEYVALUE must always come with JSITER_FOREACH */
MOZ_ASSERT_IF(flags & JSITER_KEYVALUE, flags & JSITER_FOREACH);
RootedObject obj(cx);
if (vp.isObject()) {
/* Common case. */
obj = &vp.toObject();
} else if ((flags & JSITER_ENUMERATE) && vp.isNullOrUndefined()) {
/*
* Enumerating over null and undefined gives an empty enumerator, so
* that |for (var p in <null or undefined>) <loop>;| never executes
* <loop>, per ES5 12.6.4.
*/
RootedObject iter(cx);
if (!NewEmptyPropertyIterator(cx, flags, &iter))
return false;
vp.setObject(*iter);
return true;
} else {
obj = ToObject(cx, vp);
if (!obj)
return false;
}
RootedObject iter(cx);
if (!GetIterator(cx, obj, flags, &iter))
return false;
vp.setObject(*iter);
return true;
}
bool
js::CloseIterator(JSContext* cx, HandleObject obj)
{
if (obj->is<PropertyIteratorObject>()) {
/* Remove enumerators from the active list, which is a stack. */
NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator();
if (ni->flags & JSITER_ENUMERATE) {
ni->unlink();
MOZ_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<LegacyGeneratorObject>()) {
Rooted<LegacyGeneratorObject*> genObj(cx, &obj->as<LegacyGeneratorObject>());
if (genObj->isClosed())
return true;
if (genObj->isRunning() || genObj->isClosing()) {
// Nothing sensible to do.
return true;
}
return LegacyGeneratorObject::close(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<PropertyIteratorObject>()) {
NativeIterator* ni = obj->as<PropertyIteratorObject>().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<typename StringPredicate>
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. */
HeapPtrFlatString* props_cursor = ni->current();
HeapPtrFlatString* props_end = ni->end();
for (HeapPtrFlatString* 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 (!GetPrototype(cx, obj, &proto))
return false;
if (proto) {
RootedId id(cx);
RootedValue idv(cx, StringValue(*idp));
if (!ValueToId<CanGC>(cx, idv, &id))
return false;
Rooted<PropertyDescriptor> desc(cx);
if (!GetPropertyDescriptor(cx, proto, id, &desc))
return false;
if (desc.object()) {
if (desc.enumerable())
continue;
}
}
/*
* If GetPropertyDescriptorById 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 (HeapPtrFlatString* 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<JSFlatString*> str;
public:
explicit SingleStringPredicate(Handle<JSFlatString*> 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)
{
if (JSID_IS_SYMBOL(id))
return true;
Rooted<JSFlatString*> 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 SuppressDeletedProperty(cx, obj, id);
}
bool
js::IteratorMore(JSContext* cx, HandleObject iterobj, MutableHandleValue rval)
{
// Fast path for native iterators.
if (iterobj->is<PropertyIteratorObject>()) {
NativeIterator* ni = iterobj->as<PropertyIteratorObject>().getNativeIterator();
bool done;
if (!NativeIteratorNext(cx, ni, rval, &done))
return false;
if (done)
rval.setMagic(JS_NO_ITER_VALUE);
return true;
}
// We're reentering below and can call anything.
JS_CHECK_RECURSION(cx, return false);
// Call the iterator object's .next method.
if (!GetProperty(cx, iterobj, iterobj, cx->names().next, rval))
return false;
// We try to support the old and new iterator protocol at the same time!
if (!Invoke(cx, ObjectValue(*iterobj), rval, 0, nullptr, rval)) {
// We still check for StopIterator
if (!cx->isExceptionPending())
return false;
RootedValue exception(cx);
if (!cx->getPendingException(&exception))
return false;
if (!JS_IsStopIteration(exception))
return false;
cx->clearPendingException();
rval.setMagic(JS_NO_ITER_VALUE);
return true;
}
if (!rval.isObject()) {
// Old style generators might return primitive values
return true;
}
// If the object has both the done and value property, we assume
// it's using the new style protocol. Otherwise just return the object.
RootedObject result(cx, &rval.toObject());
bool found = false;
if (!HasProperty(cx, result, cx->names().done, &found))
return false;
if (!found)
return true;
if (!HasProperty(cx, result, cx->names().value, &found))
return false;
if (!found)
return true;
// At this point we hopefully have a new style iterator result
// 7.4.4 IteratorComplete
// Get iterResult.done
if (!GetProperty(cx, result, result, cx->names().done, rval))
return false;
bool done = ToBoolean(rval);
if (done) {
rval.setMagic(JS_NO_ITER_VALUE);
return true;
}
// 7.4.5 IteratorValue
return GetProperty(cx, result, result, cx->names().value, rval);
}
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),
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
nullptr, /* finalize */
nullptr, /* call */
stopiter_hasInstance
};
static const JSFunctionSpec iterator_proto_methods[] = {
JS_SELF_HOSTED_SYM_FN(iterator, "IteratorIdentity", 0, 0),
JS_FS_END
};
/* static */ bool
GlobalObject::initIteratorProto(JSContext* cx, Handle<GlobalObject*> global)
{
if (global->getReservedSlot(ITERATOR_PROTO).isObject())
return true;
RootedObject proto(cx, global->createBlankPrototype<PlainObject>(cx));
if (!proto || !DefinePropertiesAndFunctions(cx, proto, nullptr, iterator_proto_methods))
return false;
global->setReservedSlot(ITERATOR_PROTO, ObjectValue(*proto));
return true;
}
/* static */ bool
GlobalObject::initArrayIteratorProto(JSContext* cx, Handle<GlobalObject*> global)
{
if (global->getReservedSlot(ARRAY_ITERATOR_PROTO).isObject())
return true;
RootedObject iteratorProto(cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
if (!iteratorProto)
return false;
const Class* cls = &ArrayIteratorPrototypeClass;
RootedObject proto(cx, global->createBlankPrototypeInheriting(cx, cls, iteratorProto));
if (!proto || !DefinePropertiesAndFunctions(cx, proto, nullptr, array_iterator_methods))
return false;
global->setReservedSlot(ARRAY_ITERATOR_PROTO, ObjectValue(*proto));
return true;
}
/* static */ bool
GlobalObject::initStringIteratorProto(JSContext* cx, Handle<GlobalObject*> global)
{
if (global->getReservedSlot(STRING_ITERATOR_PROTO).isObject())
return true;
RootedObject iteratorProto(cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
if (!iteratorProto)
return false;
const Class* cls = &StringIteratorPrototypeClass;
RootedObject proto(cx, global->createBlankPrototypeInheriting(cx, cls, iteratorProto));
if (!proto || !DefinePropertiesAndFunctions(cx, proto, nullptr, string_iterator_methods))
return false;
global->setReservedSlot(STRING_ITERATOR_PROTO, ObjectValue(*proto));
return true;
}
JSObject*
js::InitLegacyIteratorClass(JSContext* cx, HandleObject obj)
{
Handle<GlobalObject*> global = obj.as<GlobalObject>();
if (global->getPrototype(JSProto_Iterator).isObject())
return &global->getPrototype(JSProto_Iterator).toObject();
RootedObject iteratorProto(cx);
iteratorProto = global->createBlankPrototype(cx, &PropertyIteratorObject::class_);
if (!iteratorProto)
return nullptr;
AutoIdVector blank(cx);
NativeIterator* ni = NativeIterator::allocateIterator(cx, 0, blank);
if (!ni)
return nullptr;
ni->init(nullptr, nullptr, 0 /* flags */, 0, 0);
iteratorProto->as<PropertyIteratorObject>().setNativeIterator(ni);
Rooted<JSFunction*> ctor(cx);
ctor = global->createConstructor(cx, IteratorConstructor, cx->names().Iterator, 2);
if (!ctor)
return nullptr;
if (!LinkConstructorAndPrototype(cx, ctor, iteratorProto))
return nullptr;
if (!DefinePropertiesAndFunctions(cx, iteratorProto, nullptr, legacy_iterator_methods))
return nullptr;
if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_Iterator,
ctor, iteratorProto))
{
return nullptr;
}
return &global->getPrototype(JSProto_Iterator).toObject();
}
JSObject*
js::InitStopIterationClass(JSContext* cx, HandleObject obj)
{
Handle<GlobalObject*> global = obj.as<GlobalObject>();
if (!global->getPrototype(JSProto_StopIteration).isObject()) {
RootedObject proto(cx, global->createBlankPrototype(cx, &StopIterationObject::class_));
if (!proto || !FreezeObject(cx, proto))
return nullptr;
// This should use a non-JSProtoKey'd slot, but this is easier for now.
if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_StopIteration, proto, proto))
return nullptr;
global->setConstructor(JSProto_StopIteration, ObjectValue(*proto));
}
return &global->getPrototype(JSProto_StopIteration).toObject();
}