mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-09-27 14:56:16 +00:00
1558 lines
49 KiB
C++
1558 lines
49 KiB
C++
/* -*- 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");
|
|
}
|
|
|
|
typedef HashSet<jsid, JsidHasher> IdSet;
|
|
|
|
static inline bool
|
|
NewKeyValuePair(JSContext* cx, jsid id, const Value& val, MutableHandleValue rval)
|
|
{
|
|
return NewValuePair(cx, IdToValue(id), val, rval);
|
|
}
|
|
|
|
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();
|
|
}
|