1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sts=4 et sw=4 tw=99:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /* JavaScript iterators. */
8 
9 #include "jsiter.h"
10 
11 #include "mozilla/ArrayUtils.h"
12 #include "mozilla/Maybe.h"
13 #include "mozilla/MemoryReporting.h"
14 #include "mozilla/PodOperations.h"
15 
16 #include "jsarray.h"
17 #include "jsatom.h"
18 #include "jscntxt.h"
19 #include "jsgc.h"
20 #include "jsobj.h"
21 #include "jsopcode.h"
22 #include "jsscript.h"
23 #include "jstypes.h"
24 #include "jsutil.h"
25 
26 #include "ds/Sort.h"
27 #include "gc/Marking.h"
28 #include "js/Proxy.h"
29 #include "vm/GeneratorObject.h"
30 #include "vm/GlobalObject.h"
31 #include "vm/Interpreter.h"
32 #include "vm/Shape.h"
33 #include "vm/StopIterationObject.h"
34 #include "vm/TypedArrayCommon.h"
35 
36 #include "jsscriptinlines.h"
37 
38 #include "vm/NativeObject-inl.h"
39 #include "vm/Stack-inl.h"
40 #include "vm/String-inl.h"
41 
42 using namespace js;
43 using namespace js::gc;
44 using JS::ForOfIterator;
45 
46 using mozilla::ArrayLength;
47 using mozilla::Maybe;
48 using mozilla::PodCopy;
49 using mozilla::PodZero;
50 
51 typedef Rooted<PropertyIteratorObject*> RootedPropertyIteratorObject;
52 
53 static const gc::AllocKind ITERATOR_FINALIZE_KIND = gc::AllocKind::OBJECT2_BACKGROUND;
54 
55 void
mark(JSTracer * trc)56 NativeIterator::mark(JSTracer* trc)
57 {
58     for (HeapPtrFlatString* str = begin(); str < end(); str++)
59         TraceEdge(trc, str, "prop");
60     if (obj)
61         TraceEdge(trc, &obj, "obj");
62 
63     for (size_t i = 0; i < guard_length; i++)
64         guard_array[i].trace(trc);
65 
66     // The SuppressDeletedPropertyHelper loop can GC, so make sure that if the
67     // GC removes any elements from the list, it won't remove this one.
68     if (iterObj_)
69         TraceManuallyBarrieredEdge(trc, &iterObj_, "iterObj");
70 }
71 
72 typedef HashSet<jsid, JsidHasher> IdSet;
73 
74 static inline bool
NewKeyValuePair(JSContext * cx,jsid id,const Value & val,MutableHandleValue rval)75 NewKeyValuePair(JSContext* cx, jsid id, const Value& val, MutableHandleValue rval)
76 {
77     JS::AutoValueArray<2> vec(cx);
78     vec[0].set(IdToValue(id));
79     vec[1].set(val);
80 
81     JSObject* aobj = NewDenseCopiedArray(cx, 2, vec.begin());
82     if (!aobj)
83         return false;
84     rval.setObject(*aobj);
85     return true;
86 }
87 
88 static inline bool
Enumerate(JSContext * cx,HandleObject pobj,jsid id,bool enumerable,unsigned flags,Maybe<IdSet> & ht,AutoIdVector * props)89 Enumerate(JSContext* cx, HandleObject pobj, jsid id,
90           bool enumerable, unsigned flags, Maybe<IdSet>& ht, AutoIdVector* props)
91 {
92     // Allow duplicate properties from Proxy's [[OwnPropertyKeys]].
93     bool proxyOwnProperty = pobj->is<ProxyObject>() && (flags & JSITER_OWNONLY);
94 
95     if (!proxyOwnProperty && (!(flags & JSITER_OWNONLY) || pobj->is<ProxyObject>() ||
96         pobj->getOps()->enumerate))
97     {
98         if (!ht) {
99             ht.emplace(cx);
100             // Most of the time there are only a handful of entries.
101             if (!ht->init(5))
102                 return false;
103         }
104 
105         // If we've already seen this, we definitely won't add it.
106         IdSet::AddPtr p = ht->lookupForAdd(id);
107         if (MOZ_UNLIKELY(!!p))
108             return true;
109 
110         // It's not necessary to add properties to the hash table at the end of
111         // the prototype chain, but custom enumeration behaviors might return
112         // duplicated properties, so always add in such cases.
113         if ((pobj->is<ProxyObject>() || pobj->getProto() || pobj->getOps()->enumerate) && !ht->add(p, id))
114             return false;
115     }
116 
117     // Symbol-keyed properties and nonenumerable properties are skipped unless
118     // the caller specifically asks for them. A caller can also filter out
119     // non-symbols by asking for JSITER_SYMBOLSONLY.
120     if (JSID_IS_SYMBOL(id) ? !(flags & JSITER_SYMBOLS) : (flags & JSITER_SYMBOLSONLY))
121         return true;
122     if (!enumerable && !(flags & JSITER_HIDDEN))
123         return true;
124 
125     return props->append(id);
126 }
127 
128 static bool
EnumerateExtraProperties(JSContext * cx,HandleObject obj,unsigned flags,Maybe<IdSet> & ht,AutoIdVector * props)129 EnumerateExtraProperties(JSContext* cx, HandleObject obj, unsigned flags, Maybe<IdSet>& ht,
130                          AutoIdVector* props)
131 {
132     MOZ_ASSERT(obj->getOps()->enumerate);
133 
134     AutoIdVector properties(cx);
135     bool enumerableOnly = !(flags & JSITER_HIDDEN);
136     if (!obj->getOps()->enumerate(cx, obj, properties, enumerableOnly))
137         return false;
138 
139     RootedId id(cx);
140     for (size_t n = 0; n < properties.length(); n++) {
141         id = properties[n];
142 
143         // The enumerate hook does not indicate whether the properties
144         // it returns are enumerable or not. Since we already passed
145         // `enumerableOnly` to the hook to filter out non-enumerable
146         // properties, it doesn't really matter what we pass here.
147         bool enumerable = true;
148         if (!Enumerate(cx, obj, id, enumerable, flags, ht, props))
149             return false;
150     }
151 
152     return true;
153 }
154 
155 static bool
SortComparatorIntegerIds(jsid a,jsid b,bool * lessOrEqualp)156 SortComparatorIntegerIds(jsid a, jsid b, bool* lessOrEqualp)
157 {
158     uint32_t indexA, indexB;
159     MOZ_ALWAYS_TRUE(IdIsIndex(a, &indexA));
160     MOZ_ALWAYS_TRUE(IdIsIndex(b, &indexB));
161     *lessOrEqualp = (indexA <= indexB);
162     return true;
163 }
164 
165 static bool
EnumerateNativeProperties(JSContext * cx,HandleNativeObject pobj,unsigned flags,Maybe<IdSet> & ht,AutoIdVector * props,Handle<UnboxedPlainObject * > unboxed=nullptr)166 EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags, Maybe<IdSet>& ht,
167                           AutoIdVector* props, Handle<UnboxedPlainObject*> unboxed = nullptr)
168 {
169     bool enumerateSymbols;
170     if (flags & JSITER_SYMBOLSONLY) {
171         enumerateSymbols = true;
172     } else {
173         /* Collect any dense elements from this object. */
174         size_t firstElemIndex = props->length();
175         size_t initlen = pobj->getDenseInitializedLength();
176         const Value* vp = pobj->getDenseElements();
177         bool hasHoles = false;
178         for (size_t i = 0; i < initlen; ++i, ++vp) {
179             if (vp->isMagic(JS_ELEMENTS_HOLE)) {
180                 hasHoles = true;
181             } else {
182                 /* Dense arrays never get so large that i would not fit into an integer id. */
183                 if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props))
184                     return false;
185             }
186         }
187 
188         /* Collect any typed array or shared typed array elements from this object. */
189         if (IsAnyTypedArray(pobj)) {
190             size_t len = AnyTypedArrayLength(pobj);
191             for (size_t i = 0; i < len; i++) {
192                 if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props))
193                     return false;
194             }
195         }
196 
197         // Collect any sparse elements from this object.
198         bool isIndexed = pobj->isIndexed();
199         if (isIndexed) {
200             // If the dense elements didn't have holes, we don't need to include
201             // them in the sort.
202             if (!hasHoles)
203                 firstElemIndex = props->length();
204 
205             for (Shape::Range<NoGC> r(pobj->lastProperty()); !r.empty(); r.popFront()) {
206                 Shape& shape = r.front();
207                 jsid id = shape.propid();
208                 uint32_t dummy;
209                 if (IdIsIndex(id, &dummy)) {
210                     if (!Enumerate(cx, pobj, id, shape.enumerable(), flags, ht, props))
211                         return false;
212                 }
213             }
214 
215             MOZ_ASSERT(firstElemIndex <= props->length());
216 
217             jsid* ids = props->begin() + firstElemIndex;
218             size_t n = props->length() - firstElemIndex;
219 
220             AutoIdVector tmp(cx);
221             if (!tmp.resize(n))
222                 return false;
223             PodCopy(tmp.begin(), ids, n);
224 
225             if (!MergeSort(ids, n, tmp.begin(), SortComparatorIntegerIds))
226                 return false;
227         }
228 
229         if (unboxed) {
230             // If |unboxed| is set then |pobj| is the expando for an unboxed
231             // plain object we are enumerating. Add the unboxed properties
232             // themselves here since they are all property names that were
233             // given to the object before any of the expando's properties.
234             MOZ_ASSERT(pobj->is<UnboxedExpandoObject>());
235             if (!EnumerateExtraProperties(cx, unboxed, flags, ht, props))
236                 return false;
237         }
238 
239         size_t initialLength = props->length();
240 
241         /* Collect all unique property names from this object's shape. */
242         bool symbolsFound = false;
243         Shape::Range<NoGC> r(pobj->lastProperty());
244         for (; !r.empty(); r.popFront()) {
245             Shape& shape = r.front();
246             jsid id = shape.propid();
247 
248             if (JSID_IS_SYMBOL(id)) {
249                 symbolsFound = true;
250                 continue;
251             }
252 
253             uint32_t dummy;
254             if (isIndexed && IdIsIndex(id, &dummy))
255                 continue;
256 
257             if (!Enumerate(cx, pobj, id, shape.enumerable(), flags, ht, props))
258                 return false;
259         }
260         ::Reverse(props->begin() + initialLength, props->end());
261 
262         enumerateSymbols = symbolsFound && (flags & JSITER_SYMBOLS);
263     }
264 
265     if (enumerateSymbols) {
266         // Do a second pass to collect symbols. ES6 draft rev 25 (2014 May 22)
267         // 9.1.12 requires that all symbols appear after all strings in the
268         // result.
269         size_t initialLength = props->length();
270         for (Shape::Range<NoGC> r(pobj->lastProperty()); !r.empty(); r.popFront()) {
271             Shape& shape = r.front();
272             jsid id = shape.propid();
273             if (JSID_IS_SYMBOL(id)) {
274                 if (!Enumerate(cx, pobj, id, shape.enumerable(), flags, ht, props))
275                     return false;
276             }
277         }
278         ::Reverse(props->begin() + initialLength, props->end());
279     }
280 
281     return true;
282 }
283 
284 #ifdef JS_MORE_DETERMINISTIC
285 
286 struct SortComparatorIds
287 {
288     JSContext*  const cx;
289 
SortComparatorIdsSortComparatorIds290     SortComparatorIds(JSContext* cx)
291       : cx(cx) {}
292 
operator ()SortComparatorIds293     bool operator()(jsid a, jsid b, bool* lessOrEqualp)
294     {
295         // Pick an arbitrary order on jsids that is as stable as possible
296         // across executions.
297         if (a == b) {
298             *lessOrEqualp = true;
299             return true;
300         }
301 
302         size_t ta = JSID_BITS(a) & JSID_TYPE_MASK;
303         size_t tb = JSID_BITS(b) & JSID_TYPE_MASK;
304         if (ta != tb) {
305             *lessOrEqualp = (ta <= tb);
306             return true;
307         }
308 
309         if (JSID_IS_INT(a)) {
310             *lessOrEqualp = (JSID_TO_INT(a) <= JSID_TO_INT(b));
311             return true;
312         }
313 
314         RootedString astr(cx), bstr(cx);
315         if (JSID_IS_SYMBOL(a)) {
316             MOZ_ASSERT(JSID_IS_SYMBOL(b));
317             JS::SymbolCode ca = JSID_TO_SYMBOL(a)->code();
318             JS::SymbolCode cb = JSID_TO_SYMBOL(b)->code();
319             if (ca != cb) {
320                 *lessOrEqualp = uint32_t(ca) <= uint32_t(cb);
321                 return true;
322             }
323             MOZ_ASSERT(ca == JS::SymbolCode::InSymbolRegistry || ca == JS::SymbolCode::UniqueSymbol);
324             astr = JSID_TO_SYMBOL(a)->description();
325             bstr = JSID_TO_SYMBOL(b)->description();
326             if (!astr || !bstr) {
327                 *lessOrEqualp = !astr;
328                 return true;
329             }
330 
331             // Fall through to string comparison on the descriptions. The sort
332             // order is nondeterministic if two different unique symbols have
333             // the same description.
334         } else {
335             astr = IdToString(cx, a);
336             if (!astr)
337                 return false;
338             bstr = IdToString(cx, b);
339             if (!bstr)
340                 return false;
341         }
342 
343         int32_t result;
344         if (!CompareStrings(cx, astr, bstr, &result))
345             return false;
346 
347         *lessOrEqualp = (result <= 0);
348         return true;
349     }
350 };
351 
352 #endif /* JS_MORE_DETERMINISTIC */
353 
354 static bool
Snapshot(JSContext * cx,HandleObject pobj_,unsigned flags,AutoIdVector * props)355 Snapshot(JSContext* cx, HandleObject pobj_, unsigned flags, AutoIdVector* props)
356 {
357     // We initialize |ht| lazily (in Enumerate()) because it ends up unused
358     // anywhere from 67--99.9% of the time.
359     Maybe<IdSet> ht;
360     RootedObject pobj(cx, pobj_);
361 
362     do {
363         if (pobj->getOps()->enumerate) {
364             if (pobj->is<UnboxedPlainObject>() && pobj->as<UnboxedPlainObject>().maybeExpando()) {
365                 // Special case unboxed objects with an expando object.
366                 RootedNativeObject expando(cx, pobj->as<UnboxedPlainObject>().maybeExpando());
367                 if (!EnumerateNativeProperties(cx, expando, flags, ht, props,
368                                                pobj.as<UnboxedPlainObject>()))
369                 {
370                     return false;
371                 }
372             } else {
373                 if (!EnumerateExtraProperties(cx, pobj, flags, ht, props))
374                     return false;
375 
376                 if (pobj->isNative()) {
377                     if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, ht, props))
378                         return false;
379                 }
380             }
381         } else if (pobj->isNative()) {
382             // Give the object a chance to resolve all lazy properties
383             if (JSEnumerateOp enumerate = pobj->getClass()->enumerate) {
384                 if (!enumerate(cx, pobj.as<NativeObject>()))
385                     return false;
386             }
387             if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, ht, props))
388                 return false;
389         } else if (pobj->is<ProxyObject>()) {
390             AutoIdVector proxyProps(cx);
391             if (flags & JSITER_HIDDEN || flags & JSITER_SYMBOLS) {
392                 // This gets all property keys, both strings and
393                 // symbols.  The call to Enumerate in the loop below
394                 // will filter out unwanted keys, per the flags.
395                 if (!Proxy::ownPropertyKeys(cx, pobj, proxyProps))
396                     return false;
397 
398                 Rooted<PropertyDescriptor> desc(cx);
399                 for (size_t n = 0, len = proxyProps.length(); n < len; n++) {
400                     bool enumerable = false;
401 
402                     // We need to filter, if the caller just wants enumerable
403                     // symbols.
404                     if (!(flags & JSITER_HIDDEN)) {
405                         if (!Proxy::getOwnPropertyDescriptor(cx, pobj, proxyProps[n], &desc))
406                             return false;
407                         enumerable = desc.enumerable();
408                     }
409 
410                     if (!Enumerate(cx, pobj, proxyProps[n], enumerable, flags, ht, props))
411                         return false;
412                 }
413             } else {
414                 // Returns enumerable property names (no symbols).
415                 if (!Proxy::getOwnEnumerablePropertyKeys(cx, pobj, proxyProps))
416                     return false;
417 
418                 for (size_t n = 0, len = proxyProps.length(); n < len; n++) {
419                     if (!Enumerate(cx, pobj, proxyProps[n], true, flags, ht, props))
420                         return false;
421                 }
422             }
423         } else {
424             MOZ_CRASH("non-native objects must have an enumerate op");
425         }
426 
427         if (flags & JSITER_OWNONLY)
428             break;
429 
430         if (!GetPrototype(cx, pobj, &pobj))
431             return false;
432 
433     } while (pobj != nullptr);
434 
435 #ifdef JS_MORE_DETERMINISTIC
436 
437     /*
438      * In some cases the enumeration order for an object depends on the
439      * execution mode (interpreter vs. JIT), especially for native objects
440      * with a class enumerate hook (where resolving a property changes the
441      * resulting enumeration order). These aren't really bugs, but the
442      * differences can change the generated output and confuse correctness
443      * fuzzers, so we sort the ids if such a fuzzer is running.
444      *
445      * We don't do this in the general case because (a) doing so is slow,
446      * and (b) it also breaks the web, which expects enumeration order to
447      * follow the order in which properties are added, in certain cases.
448      * Since ECMA does not specify an enumeration order for objects, both
449      * behaviors are technically correct to do.
450      */
451 
452     jsid* ids = props->begin();
453     size_t n = props->length();
454 
455     AutoIdVector tmp(cx);
456     if (!tmp.resize(n))
457         return false;
458     PodCopy(tmp.begin(), ids, n);
459 
460     if (!MergeSort(ids, n, tmp.begin(), SortComparatorIds(cx)))
461         return false;
462 
463 #endif /* JS_MORE_DETERMINISTIC */
464 
465     return true;
466 }
467 
JS_FRIEND_API(bool)468 JS_FRIEND_API(bool)
469 js::GetPropertyKeys(JSContext* cx, HandleObject obj, unsigned flags, AutoIdVector* props)
470 {
471     return Snapshot(cx, obj,
472                     flags & (JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | JSITER_SYMBOLSONLY),
473                     props);
474 }
475 
476 size_t sCustomIteratorCount = 0;
477 
478 static inline bool
GetCustomIterator(JSContext * cx,HandleObject obj,unsigned flags,MutableHandleObject objp)479 GetCustomIterator(JSContext* cx, HandleObject obj, unsigned flags, MutableHandleObject objp)
480 {
481     JS_CHECK_RECURSION(cx, return false);
482 
483     RootedValue rval(cx);
484     /* Check whether we have a valid __iterator__ method. */
485     HandlePropertyName name = cx->names().iteratorIntrinsic;
486     if (!GetProperty(cx, obj, obj, name, &rval))
487         return false;
488 
489     /* If there is no custom __iterator__ method, we are done here. */
490     if (!rval.isObject()) {
491         objp.set(nullptr);
492         return true;
493     }
494 
495     if (!cx->runningWithTrustedPrincipals())
496         ++sCustomIteratorCount;
497 
498     /* Otherwise call it and return that object. */
499     Value arg = BooleanValue((flags & JSITER_FOREACH) == 0);
500     if (!Invoke(cx, ObjectValue(*obj), rval, 1, &arg, &rval))
501         return false;
502     if (rval.isPrimitive()) {
503         // Ignore the stack when throwing. We can't tell whether we were
504         // supposed to skip over a new.target or not.
505         JSAutoByteString bytes;
506         if (!AtomToPrintableString(cx, name, &bytes))
507             return false;
508         RootedValue val(cx, ObjectValue(*obj));
509         ReportValueError2(cx, JSMSG_BAD_TRAP_RETURN_VALUE,
510                           JSDVG_IGNORE_STACK, val, nullptr, bytes.ptr());
511         return false;
512     }
513     objp.set(&rval.toObject());
514     return true;
515 }
516 
517 template <typename T>
518 static inline bool
Compare(T * a,T * b,size_t c)519 Compare(T* a, T* b, size_t c)
520 {
521     size_t n = (c + size_t(7)) / size_t(8);
522     switch (c % 8) {
523       case 0: do { if (*a++ != *b++) return false;
524       case 7:      if (*a++ != *b++) return false;
525       case 6:      if (*a++ != *b++) return false;
526       case 5:      if (*a++ != *b++) return false;
527       case 4:      if (*a++ != *b++) return false;
528       case 3:      if (*a++ != *b++) return false;
529       case 2:      if (*a++ != *b++) return false;
530       case 1:      if (*a++ != *b++) return false;
531               } while (--n > 0);
532     }
533     return true;
534 }
535 
536 static bool legacy_iterator_next(JSContext* cx, unsigned argc, Value* vp);
537 
538 static inline PropertyIteratorObject*
NewPropertyIteratorObject(JSContext * cx,unsigned flags)539 NewPropertyIteratorObject(JSContext* cx, unsigned flags)
540 {
541     if (flags & JSITER_ENUMERATE) {
542         RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &PropertyIteratorObject::class_,
543                                                                  TaggedProto(nullptr)));
544         if (!group)
545             return nullptr;
546 
547         const Class* clasp = &PropertyIteratorObject::class_;
548         RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(nullptr),
549                                                           ITERATOR_FINALIZE_KIND));
550         if (!shape)
551             return nullptr;
552 
553         JSObject* obj = JSObject::create(cx, ITERATOR_FINALIZE_KIND,
554                                          GetInitialHeap(GenericObject, clasp), shape, group);
555         if (!obj)
556             return nullptr;
557 
558         PropertyIteratorObject* res = &obj->as<PropertyIteratorObject>();
559 
560         MOZ_ASSERT(res->numFixedSlots() == JSObject::ITER_CLASS_NFIXED_SLOTS);
561         return res;
562     }
563 
564     Rooted<PropertyIteratorObject*> res(cx, NewBuiltinClassInstance<PropertyIteratorObject>(cx));
565     if (!res)
566         return nullptr;
567 
568     if (flags == 0) {
569         // Redefine next as an own property. This ensure that deleting the
570         // next method on the prototype doesn't break cross-global for .. in.
571         // We don't have to do this for JSITER_ENUMERATE because that object always
572         // takes an optimized path.
573         RootedFunction next(cx, NewNativeFunction(cx, legacy_iterator_next, 0,
574                                                   HandlePropertyName(cx->names().next)));
575         if (!next)
576             return nullptr;
577 
578         RootedValue value(cx, ObjectValue(*next));
579         if (!DefineProperty(cx, res, cx->names().next, value))
580             return nullptr;
581     }
582 
583     return res;
584 }
585 
586 NativeIterator*
allocateIterator(JSContext * cx,uint32_t numGuards,const AutoIdVector & props)587 NativeIterator::allocateIterator(JSContext* cx, uint32_t numGuards, const AutoIdVector& props)
588 {
589     JS_STATIC_ASSERT(sizeof(ReceiverGuard) == 2 * sizeof(void*));
590 
591     size_t plength = props.length();
592     NativeIterator* ni = cx->zone()->pod_malloc_with_extra<NativeIterator, void*>(plength + numGuards * 2);
593     if (!ni) {
594         ReportOutOfMemory(cx);
595         return nullptr;
596     }
597 
598     AutoValueVector strings(cx);
599     ni->props_array = ni->props_cursor = reinterpret_cast<HeapPtrFlatString*>(ni + 1);
600     ni->props_end = ni->props_array + plength;
601     if (plength) {
602         for (size_t i = 0; i < plength; i++) {
603             JSFlatString* str = IdToString(cx, props[i]);
604             if (!str || !strings.append(StringValue(str)))
605                 return nullptr;
606             ni->props_array[i].init(str);
607         }
608     }
609     ni->next_ = nullptr;
610     ni->prev_ = nullptr;
611     return ni;
612 }
613 
614 NativeIterator*
allocateSentinel(JSContext * maybecx)615 NativeIterator::allocateSentinel(JSContext* maybecx)
616 {
617     NativeIterator* ni = js_pod_malloc<NativeIterator>();
618     if (!ni) {
619         if (maybecx)
620             ReportOutOfMemory(maybecx);
621         return nullptr;
622     }
623 
624     PodZero(ni);
625 
626     ni->next_ = ni;
627     ni->prev_ = ni;
628     return ni;
629 }
630 
631 inline void
init(JSObject * obj,JSObject * iterObj,unsigned flags,uint32_t numGuards,uint32_t key)632 NativeIterator::init(JSObject* obj, JSObject* iterObj, unsigned flags, uint32_t numGuards, uint32_t key)
633 {
634     this->obj.init(obj);
635     this->iterObj_ = iterObj;
636     this->flags = flags;
637     this->guard_array = (HeapReceiverGuard*) this->props_end;
638     this->guard_length = numGuards;
639     this->guard_key = key;
640 }
641 
642 static inline void
RegisterEnumerator(JSContext * cx,PropertyIteratorObject * iterobj,NativeIterator * ni)643 RegisterEnumerator(JSContext* cx, PropertyIteratorObject* iterobj, NativeIterator* ni)
644 {
645     /* Register non-escaping native enumerators (for-in) with the current context. */
646     if (ni->flags & JSITER_ENUMERATE) {
647         ni->link(cx->compartment()->enumerators);
648 
649         MOZ_ASSERT(!(ni->flags & JSITER_ACTIVE));
650         ni->flags |= JSITER_ACTIVE;
651     }
652 }
653 
654 static inline bool
VectorToKeyIterator(JSContext * cx,HandleObject obj,unsigned flags,AutoIdVector & keys,uint32_t numGuards,uint32_t key,MutableHandleObject objp)655 VectorToKeyIterator(JSContext* cx, HandleObject obj, unsigned flags, AutoIdVector& keys,
656                     uint32_t numGuards, uint32_t key, MutableHandleObject objp)
657 {
658     MOZ_ASSERT(!(flags & JSITER_FOREACH));
659 
660     if (obj->isSingleton() && !obj->setIteratedSingleton(cx))
661         return false;
662     MarkObjectGroupFlags(cx, obj, OBJECT_FLAG_ITERATED);
663 
664     Rooted<PropertyIteratorObject*> iterobj(cx, NewPropertyIteratorObject(cx, flags));
665     if (!iterobj)
666         return false;
667 
668     NativeIterator* ni = NativeIterator::allocateIterator(cx, numGuards, keys);
669     if (!ni)
670         return false;
671     ni->init(obj, iterobj, flags, numGuards, key);
672 
673     if (numGuards) {
674         // Fill in the guard array from scratch.
675         JSObject* pobj = obj;
676         size_t ind = 0;
677         do {
678             ni->guard_array[ind++].init(ReceiverGuard(pobj));
679             pobj = pobj->getProto();
680         } while (pobj);
681         MOZ_ASSERT(ind == numGuards);
682     }
683 
684     iterobj->setNativeIterator(ni);
685     objp.set(iterobj);
686 
687     RegisterEnumerator(cx, iterobj, ni);
688     return true;
689 }
690 
691 static bool
VectorToValueIterator(JSContext * cx,HandleObject obj,unsigned flags,AutoIdVector & keys,MutableHandleObject objp)692 VectorToValueIterator(JSContext* cx, HandleObject obj, unsigned flags, AutoIdVector& keys,
693                       MutableHandleObject objp)
694 {
695     MOZ_ASSERT(flags & JSITER_FOREACH);
696 
697     if (obj->isSingleton() && !obj->setIteratedSingleton(cx))
698         return false;
699     MarkObjectGroupFlags(cx, obj, OBJECT_FLAG_ITERATED);
700 
701     Rooted<PropertyIteratorObject*> iterobj(cx, NewPropertyIteratorObject(cx, flags));
702     if (!iterobj)
703         return false;
704 
705     NativeIterator* ni = NativeIterator::allocateIterator(cx, 0, keys);
706     if (!ni)
707         return false;
708     ni->init(obj, iterobj, flags, 0, 0);
709 
710     iterobj->setNativeIterator(ni);
711     objp.set(iterobj);
712 
713     RegisterEnumerator(cx, iterobj, ni);
714     return true;
715 }
716 
717 bool
EnumeratedIdVectorToIterator(JSContext * cx,HandleObject obj,unsigned flags,AutoIdVector & props,MutableHandleObject objp)718 js::EnumeratedIdVectorToIterator(JSContext* cx, HandleObject obj, unsigned flags,
719                                  AutoIdVector& props, MutableHandleObject objp)
720 {
721     if (!(flags & JSITER_FOREACH))
722         return VectorToKeyIterator(cx, obj, flags, props, 0, 0, objp);
723 
724     return VectorToValueIterator(cx, obj, flags, props, objp);
725 }
726 
727 // Mainly used for .. in over null/undefined
728 bool
NewEmptyPropertyIterator(JSContext * cx,unsigned flags,MutableHandleObject objp)729 js::NewEmptyPropertyIterator(JSContext* cx, unsigned flags, MutableHandleObject objp)
730 {
731     Rooted<PropertyIteratorObject*> iterobj(cx, NewPropertyIteratorObject(cx, flags));
732     if (!iterobj)
733         return false;
734 
735     AutoIdVector keys(cx); // Empty
736     NativeIterator* ni = NativeIterator::allocateIterator(cx, 0, keys);
737     if (!ni)
738         return false;
739     ni->init(nullptr, iterobj, flags, 0, 0);
740 
741     iterobj->setNativeIterator(ni);
742     objp.set(iterobj);
743 
744     RegisterEnumerator(cx, iterobj, ni);
745     return true;
746 }
747 
748 static inline void
UpdateNativeIterator(NativeIterator * ni,JSObject * obj)749 UpdateNativeIterator(NativeIterator* ni, JSObject* obj)
750 {
751     // Update the object for which the native iterator is associated, so
752     // SuppressDeletedPropertyHelper will recognize the iterator as a match.
753     ni->obj = obj;
754 }
755 
756 static inline bool
CanCompareIterableObjectToCache(JSObject * obj)757 CanCompareIterableObjectToCache(JSObject* obj)
758 {
759     if (obj->isNative())
760         return obj->as<NativeObject>().hasEmptyElements();
761     if (obj->is<UnboxedPlainObject>()) {
762         if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
763             return expando->hasEmptyElements();
764         return true;
765     }
766     return false;
767 }
768 
769 static inline bool
CanCacheIterableObject(JSContext * cx,JSObject * obj)770 CanCacheIterableObject(JSContext* cx, JSObject* obj)
771 {
772     if (!CanCompareIterableObjectToCache(obj))
773         return false;
774     if (obj->isNative()) {
775         if (IsAnyTypedArray(obj) ||
776             obj->hasUncacheableProto() ||
777             obj->getOps()->enumerate ||
778             obj->getClass()->enumerate ||
779             obj->as<NativeObject>().containsPure(cx->names().iteratorIntrinsic))
780         {
781             return false;
782         }
783     }
784     return true;
785 }
786 
787 bool
GetIterator(JSContext * cx,HandleObject obj,unsigned flags,MutableHandleObject objp)788 js::GetIterator(JSContext* cx, HandleObject obj, unsigned flags, MutableHandleObject objp)
789 {
790     if (obj->is<PropertyIteratorObject>() || obj->is<LegacyGeneratorObject>()) {
791         objp.set(obj);
792         return true;
793     }
794 
795     // We should only call the enumerate trap for "for-in".
796     // Or when we call GetIterator from the Proxy [[Enumerate]] hook.
797     // In the future also for Reflect.enumerate.
798     // JSITER_ENUMERATE is just an optimization and the same
799     // as flags == 0 otherwise.
800     if (flags == 0 || flags == JSITER_ENUMERATE) {
801         if (obj->is<ProxyObject>())
802             return Proxy::enumerate(cx, obj, objp);
803     }
804 
805     Vector<ReceiverGuard, 8> guards(cx);
806     uint32_t key = 0;
807     if (flags == JSITER_ENUMERATE) {
808         // Check to see if this is the same as the most recent object which was
809         // iterated over.
810         PropertyIteratorObject* last = cx->runtime()->nativeIterCache.last;
811         if (last) {
812             NativeIterator* lastni = last->getNativeIterator();
813             if (!(lastni->flags & (JSITER_ACTIVE|JSITER_UNREUSABLE)) &&
814                 CanCompareIterableObjectToCache(obj) &&
815                 ReceiverGuard(obj) == lastni->guard_array[0])
816             {
817                 JSObject* proto = obj->getProto();
818                 if (CanCompareIterableObjectToCache(proto) &&
819                     ReceiverGuard(proto) == lastni->guard_array[1] &&
820                     !proto->getProto())
821                 {
822                     objp.set(last);
823                     UpdateNativeIterator(lastni, obj);
824                     RegisterEnumerator(cx, last, lastni);
825                     return true;
826                 }
827             }
828         }
829 
830         /*
831          * The iterator object for JSITER_ENUMERATE never escapes, so we
832          * don't care for the proper parent/proto to be set. This also
833          * allows us to re-use a previous iterator object that is not
834          * currently active.
835          */
836         {
837             JSObject* pobj = obj;
838             do {
839                 if (!CanCacheIterableObject(cx, pobj)) {
840                     guards.clear();
841                     goto miss;
842                 }
843                 ReceiverGuard guard(pobj);
844                 key = (key + (key << 16)) ^ guard.hash();
845                 if (!guards.append(guard))
846                     return false;
847                 pobj = pobj->getProto();
848             } while (pobj);
849         }
850 
851         PropertyIteratorObject* iterobj = cx->runtime()->nativeIterCache.get(key);
852         if (iterobj) {
853             NativeIterator* ni = iterobj->getNativeIterator();
854             if (!(ni->flags & (JSITER_ACTIVE|JSITER_UNREUSABLE)) &&
855                 ni->guard_key == key &&
856                 ni->guard_length == guards.length() &&
857                 Compare(reinterpret_cast<ReceiverGuard*>(ni->guard_array),
858                         guards.begin(), ni->guard_length))
859             {
860                 objp.set(iterobj);
861 
862                 UpdateNativeIterator(ni, obj);
863                 RegisterEnumerator(cx, iterobj, ni);
864                 if (guards.length() == 2)
865                     cx->runtime()->nativeIterCache.last = iterobj;
866                 return true;
867             }
868         }
869     }
870 
871   miss:
872     if (!GetCustomIterator(cx, obj, flags, objp))
873         return false;
874     if (objp)
875         return true;
876 
877     AutoIdVector keys(cx);
878     if (flags & JSITER_FOREACH) {
879         MOZ_ASSERT(guards.empty());
880 
881         if (!Snapshot(cx, obj, flags, &keys))
882             return false;
883         if (!VectorToValueIterator(cx, obj, flags, keys, objp))
884             return false;
885     } else {
886         if (!Snapshot(cx, obj, flags, &keys))
887             return false;
888         if (!VectorToKeyIterator(cx, obj, flags, keys, guards.length(), key, objp))
889             return false;
890     }
891 
892     PropertyIteratorObject* iterobj = &objp->as<PropertyIteratorObject>();
893 
894     /* Cache the iterator object if possible. */
895     if (guards.length())
896         cx->runtime()->nativeIterCache.set(key, iterobj);
897 
898     if (guards.length() == 2)
899         cx->runtime()->nativeIterCache.last = iterobj;
900     return true;
901 }
902 
903 JSObject*
GetIteratorObject(JSContext * cx,HandleObject obj,uint32_t flags)904 js::GetIteratorObject(JSContext* cx, HandleObject obj, uint32_t flags)
905 {
906     RootedObject iterator(cx);
907     if (!GetIterator(cx, obj, flags, &iterator))
908         return nullptr;
909     return iterator;
910 }
911 
912 JSObject*
CreateItrResultObject(JSContext * cx,HandleValue value,bool done)913 js::CreateItrResultObject(JSContext* cx, HandleValue value, bool done)
914 {
915     // FIXME: We can cache the iterator result object shape somewhere.
916     AssertHeapIsIdle(cx);
917 
918     RootedObject proto(cx, cx->global()->getOrCreateObjectPrototype(cx));
919     if (!proto)
920         return nullptr;
921 
922     RootedPlainObject obj(cx, NewObjectWithGivenProto<PlainObject>(cx, proto));
923     if (!obj)
924         return nullptr;
925 
926     if (!DefineProperty(cx, obj, cx->names().value, value))
927         return nullptr;
928 
929     RootedValue doneBool(cx, BooleanValue(done));
930     if (!DefineProperty(cx, obj, cx->names().done, doneBool))
931         return nullptr;
932 
933     return obj;
934 }
935 
936 bool
ThrowStopIteration(JSContext * cx)937 js::ThrowStopIteration(JSContext* cx)
938 {
939     MOZ_ASSERT(!JS_IsExceptionPending(cx));
940 
941     // StopIteration isn't a constructor, but it's stored in GlobalObject
942     // as one, out of laziness. Hence the GetBuiltinConstructor call here.
943     RootedObject ctor(cx);
944     if (GetBuiltinConstructor(cx, JSProto_StopIteration, &ctor))
945         cx->setPendingException(ObjectValue(*ctor));
946     return false;
947 }
948 
949 /*** Iterator objects ****************************************************************************/
950 
951 bool
IteratorConstructor(JSContext * cx,unsigned argc,Value * vp)952 js::IteratorConstructor(JSContext* cx, unsigned argc, Value* vp)
953 {
954     CallArgs args = CallArgsFromVp(argc, vp);
955     if (args.length() == 0) {
956         ReportMissingArg(cx, args.calleev(), 0);
957         return false;
958     }
959 
960     bool keyonly = false;
961     if (args.length() >= 2)
962         keyonly = ToBoolean(args[1]);
963     unsigned flags = JSITER_OWNONLY | (keyonly ? 0 : (JSITER_FOREACH | JSITER_KEYVALUE));
964 
965     if (!ValueToIterator(cx, flags, args[0]))
966         return false;
967     args.rval().set(args[0]);
968     return true;
969 }
970 
971 MOZ_ALWAYS_INLINE bool
NativeIteratorNext(JSContext * cx,NativeIterator * ni,MutableHandleValue rval,bool * done)972 NativeIteratorNext(JSContext* cx, NativeIterator* ni, MutableHandleValue rval, bool* done)
973 {
974     *done = false;
975 
976     if (ni->props_cursor >= ni->props_end) {
977         *done = true;
978         return true;
979     }
980 
981     if (MOZ_LIKELY(ni->isKeyIter())) {
982         rval.setString(*ni->current());
983         ni->incCursor();
984         return true;
985     }
986 
987     // Non-standard Iterator for "for each"
988     RootedId id(cx);
989     RootedValue current(cx, StringValue(*ni->current()));
990     if (!ValueToId<CanGC>(cx, current, &id))
991         return false;
992     ni->incCursor();
993     RootedObject obj(cx, ni->obj);
994     if (!GetProperty(cx, obj, obj, id, rval))
995         return false;
996 
997     // JS 1.7 only: for each (let [k, v] in obj)
998     if (ni->flags & JSITER_KEYVALUE)
999         return NewKeyValuePair(cx, id, rval, rval);
1000     return true;
1001 }
1002 
1003 MOZ_ALWAYS_INLINE bool
IsIterator(HandleValue v)1004 IsIterator(HandleValue v)
1005 {
1006     return v.isObject() && v.toObject().hasClass(&PropertyIteratorObject::class_);
1007 }
1008 
1009 MOZ_ALWAYS_INLINE bool
legacy_iterator_next_impl(JSContext * cx,const CallArgs & args)1010 legacy_iterator_next_impl(JSContext* cx, const CallArgs& args)
1011 {
1012     MOZ_ASSERT(IsIterator(args.thisv()));
1013 
1014     RootedObject thisObj(cx, &args.thisv().toObject());
1015 
1016     NativeIterator* ni = thisObj.as<PropertyIteratorObject>()->getNativeIterator();
1017     RootedValue value(cx);
1018     bool done;
1019     if (!NativeIteratorNext(cx, ni, &value, &done))
1020          return false;
1021 
1022     // Use old iterator protocol for compatibility reasons.
1023     if (done) {
1024         ThrowStopIteration(cx);
1025         return false;
1026     }
1027 
1028     args.rval().set(value);
1029     return true;
1030 }
1031 
1032 static bool
legacy_iterator_next(JSContext * cx,unsigned argc,Value * vp)1033 legacy_iterator_next(JSContext* cx, unsigned argc, Value* vp)
1034 {
1035     CallArgs args = CallArgsFromVp(argc, vp);
1036     return CallNonGenericMethod<IsIterator, legacy_iterator_next_impl>(cx, args);
1037 }
1038 
1039 static const JSFunctionSpec legacy_iterator_methods[] = {
1040     JS_SELF_HOSTED_SYM_FN(iterator, "LegacyIteratorShim", 0, 0),
1041     JS_FN("next",      legacy_iterator_next,       0, 0),
1042     JS_FS_END
1043 };
1044 
1045 size_t
sizeOfMisc(mozilla::MallocSizeOf mallocSizeOf) const1046 PropertyIteratorObject::sizeOfMisc(mozilla::MallocSizeOf mallocSizeOf) const
1047 {
1048     return mallocSizeOf(getPrivate());
1049 }
1050 
1051 void
trace(JSTracer * trc,JSObject * obj)1052 PropertyIteratorObject::trace(JSTracer* trc, JSObject* obj)
1053 {
1054     if (NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator())
1055         ni->mark(trc);
1056 }
1057 
1058 void
finalize(FreeOp * fop,JSObject * obj)1059 PropertyIteratorObject::finalize(FreeOp* fop, JSObject* obj)
1060 {
1061     if (NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator())
1062         fop->free_(ni);
1063 }
1064 
1065 const Class PropertyIteratorObject::class_ = {
1066     "Iterator",
1067     JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator) |
1068     JSCLASS_HAS_PRIVATE |
1069     JSCLASS_BACKGROUND_FINALIZE,
1070     nullptr, /* addProperty */
1071     nullptr, /* delProperty */
1072     nullptr, /* getProperty */
1073     nullptr, /* setProperty */
1074     nullptr, /* enumerate */
1075     nullptr, /* resolve */
1076     nullptr, /* mayResolve */
1077     finalize,
1078     nullptr, /* call        */
1079     nullptr, /* hasInstance */
1080     nullptr, /* construct   */
1081     trace
1082 };
1083 
1084 static const Class ArrayIteratorPrototypeClass = {
1085     "Array Iterator",
1086     0
1087 };
1088 
1089 enum {
1090     ArrayIteratorSlotIteratedObject,
1091     ArrayIteratorSlotNextIndex,
1092     ArrayIteratorSlotItemKind,
1093     ArrayIteratorSlotCount
1094 };
1095 
1096 const Class ArrayIteratorObject::class_ = {
1097     "Array Iterator",
1098     JSCLASS_HAS_RESERVED_SLOTS(ArrayIteratorSlotCount)
1099 };
1100 
1101 static const JSFunctionSpec array_iterator_methods[] = {
1102     JS_SELF_HOSTED_FN("next", "ArrayIteratorNext", 0, 0),
1103     JS_FS_END
1104 };
1105 
1106 static const Class StringIteratorPrototypeClass = {
1107     "String Iterator",
1108     0
1109 };
1110 
1111 enum {
1112     StringIteratorSlotIteratedObject,
1113     StringIteratorSlotNextIndex,
1114     StringIteratorSlotCount
1115 };
1116 
1117 const Class StringIteratorObject::class_ = {
1118     "String Iterator",
1119     JSCLASS_HAS_RESERVED_SLOTS(StringIteratorSlotCount)
1120 };
1121 
1122 static const JSFunctionSpec string_iterator_methods[] = {
1123     JS_SELF_HOSTED_FN("next", "StringIteratorNext", 0, 0),
1124     JS_FS_END
1125 };
1126 
1127 enum {
1128     ListIteratorSlotIteratedObject,
1129     ListIteratorSlotNextIndex,
1130     ListIteratorSlotNextMethod,
1131     ListIteratorSlotCount
1132 };
1133 
1134 const Class ListIteratorObject::class_ = {
1135     "List Iterator",
1136     JSCLASS_HAS_RESERVED_SLOTS(ListIteratorSlotCount)
1137 };
1138 
1139 bool
ValueToIterator(JSContext * cx,unsigned flags,MutableHandleValue vp)1140 js::ValueToIterator(JSContext* cx, unsigned flags, MutableHandleValue vp)
1141 {
1142     /* JSITER_KEYVALUE must always come with JSITER_FOREACH */
1143     MOZ_ASSERT_IF(flags & JSITER_KEYVALUE, flags & JSITER_FOREACH);
1144 
1145     RootedObject obj(cx);
1146     if (vp.isObject()) {
1147         /* Common case. */
1148         obj = &vp.toObject();
1149     } else if ((flags & JSITER_ENUMERATE) && vp.isNullOrUndefined()) {
1150         /*
1151          * Enumerating over null and undefined gives an empty enumerator, so
1152          * that |for (var p in <null or undefined>) <loop>;| never executes
1153          * <loop>, per ES5 12.6.4.
1154          */
1155         RootedObject iter(cx);
1156         if (!NewEmptyPropertyIterator(cx, flags, &iter))
1157             return false;
1158         vp.setObject(*iter);
1159         return true;
1160     } else {
1161         obj = ToObject(cx, vp);
1162         if (!obj)
1163             return false;
1164     }
1165 
1166     RootedObject iter(cx);
1167     if (!GetIterator(cx, obj, flags, &iter))
1168         return false;
1169     vp.setObject(*iter);
1170     return true;
1171 }
1172 
1173 bool
CloseIterator(JSContext * cx,HandleObject obj)1174 js::CloseIterator(JSContext* cx, HandleObject obj)
1175 {
1176     if (obj->is<PropertyIteratorObject>()) {
1177         /* Remove enumerators from the active list, which is a stack. */
1178         NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator();
1179 
1180         if (ni->flags & JSITER_ENUMERATE) {
1181             ni->unlink();
1182 
1183             MOZ_ASSERT(ni->flags & JSITER_ACTIVE);
1184             ni->flags &= ~JSITER_ACTIVE;
1185 
1186             /*
1187              * Reset the enumerator; it may still be in the cached iterators
1188              * for this thread, and can be reused.
1189              */
1190             ni->props_cursor = ni->props_array;
1191         }
1192     } else if (obj->is<LegacyGeneratorObject>()) {
1193         Rooted<LegacyGeneratorObject*> genObj(cx, &obj->as<LegacyGeneratorObject>());
1194         if (genObj->isClosed())
1195             return true;
1196         if (genObj->isRunning() || genObj->isClosing()) {
1197             // Nothing sensible to do.
1198             return true;
1199         }
1200         return LegacyGeneratorObject::close(cx, obj);
1201     }
1202     return true;
1203 }
1204 
1205 bool
UnwindIteratorForException(JSContext * cx,HandleObject obj)1206 js::UnwindIteratorForException(JSContext* cx, HandleObject obj)
1207 {
1208     RootedValue v(cx);
1209     bool getOk = cx->getPendingException(&v);
1210     cx->clearPendingException();
1211     if (!CloseIterator(cx, obj))
1212         return false;
1213     if (!getOk)
1214         return false;
1215     cx->setPendingException(v);
1216     return true;
1217 }
1218 
1219 void
UnwindIteratorForUncatchableException(JSContext * cx,JSObject * obj)1220 js::UnwindIteratorForUncatchableException(JSContext* cx, JSObject* obj)
1221 {
1222     if (obj->is<PropertyIteratorObject>()) {
1223         NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator();
1224         if (ni->flags & JSITER_ENUMERATE)
1225             ni->unlink();
1226     }
1227 }
1228 
1229 /*
1230  * Suppress enumeration of deleted properties. This function must be called
1231  * when a property is deleted and there might be active enumerators.
1232  *
1233  * We maintain a list of active non-escaping for-in enumerators. To suppress
1234  * a property, we check whether each active enumerator contains the (obj, id)
1235  * pair and has not yet enumerated |id|. If so, and |id| is the next property,
1236  * we simply advance the cursor. Otherwise, we delete |id| from the list.
1237  *
1238  * We do not suppress enumeration of a property deleted along an object's
1239  * prototype chain. Only direct deletions on the object are handled.
1240  *
1241  * This function can suppress multiple properties at once. The |predicate|
1242  * argument is an object which can be called on an id and returns true or
1243  * false. It also must have a method |matchesAtMostOne| which allows us to
1244  * stop searching after the first deletion if true.
1245  */
1246 template<typename StringPredicate>
1247 static bool
SuppressDeletedPropertyHelper(JSContext * cx,HandleObject obj,StringPredicate predicate)1248 SuppressDeletedPropertyHelper(JSContext* cx, HandleObject obj, StringPredicate predicate)
1249 {
1250     NativeIterator* enumeratorList = cx->compartment()->enumerators;
1251     NativeIterator* ni = enumeratorList->next();
1252 
1253     while (ni != enumeratorList) {
1254       again:
1255         /* This only works for identified suppressed keys, not values. */
1256         if (ni->isKeyIter() && ni->obj == obj && ni->props_cursor < ni->props_end) {
1257             /* Check whether id is still to come. */
1258             HeapPtrFlatString* props_cursor = ni->current();
1259             HeapPtrFlatString* props_end = ni->end();
1260             for (HeapPtrFlatString* idp = props_cursor; idp < props_end; ++idp) {
1261                 if (predicate(*idp)) {
1262                     /*
1263                      * Check whether another property along the prototype chain
1264                      * became visible as a result of this deletion.
1265                      */
1266                     RootedObject proto(cx);
1267                     if (!GetPrototype(cx, obj, &proto))
1268                         return false;
1269                     if (proto) {
1270                         RootedId id(cx);
1271                         RootedValue idv(cx, StringValue(*idp));
1272                         if (!ValueToId<CanGC>(cx, idv, &id))
1273                             return false;
1274 
1275                         Rooted<PropertyDescriptor> desc(cx);
1276                         if (!GetPropertyDescriptor(cx, proto, id, &desc))
1277                             return false;
1278 
1279                         if (desc.object()) {
1280                             if (desc.enumerable())
1281                                 continue;
1282                         }
1283                     }
1284 
1285                     /*
1286                      * If GetPropertyDescriptorById above removed a property from
1287                      * ni, start over.
1288                      */
1289                     if (props_end != ni->props_end || props_cursor != ni->props_cursor)
1290                         goto again;
1291 
1292                     /*
1293                      * No property along the prototype chain stepped in to take the
1294                      * property's place, so go ahead and delete id from the list.
1295                      * If it is the next property to be enumerated, just skip it.
1296                      */
1297                     if (idp == props_cursor) {
1298                         ni->incCursor();
1299                     } else {
1300                         for (HeapPtrFlatString* p = idp; p + 1 != props_end; p++)
1301                             *p = *(p + 1);
1302                         ni->props_end = ni->end() - 1;
1303 
1304                         /*
1305                          * This invokes the pre barrier on this element, since
1306                          * it's no longer going to be marked, and ensures that
1307                          * any existing remembered set entry will be dropped.
1308                          */
1309                         *ni->props_end = nullptr;
1310                     }
1311 
1312                     /* Don't reuse modified native iterators. */
1313                     ni->flags |= JSITER_UNREUSABLE;
1314 
1315                     if (predicate.matchesAtMostOne())
1316                         break;
1317                 }
1318             }
1319         }
1320         ni = ni->next();
1321     }
1322     return true;
1323 }
1324 
1325 namespace {
1326 
1327 class SingleStringPredicate {
1328     Handle<JSFlatString*> str;
1329 public:
SingleStringPredicate(Handle<JSFlatString * > str)1330     explicit SingleStringPredicate(Handle<JSFlatString*> str) : str(str) {}
1331 
operator ()(JSFlatString * str)1332     bool operator()(JSFlatString* str) { return EqualStrings(str, this->str); }
matchesAtMostOne()1333     bool matchesAtMostOne() { return true; }
1334 };
1335 
1336 } /* anonymous namespace */
1337 
1338 bool
SuppressDeletedProperty(JSContext * cx,HandleObject obj,jsid id)1339 js::SuppressDeletedProperty(JSContext* cx, HandleObject obj, jsid id)
1340 {
1341     if (JSID_IS_SYMBOL(id))
1342         return true;
1343 
1344     Rooted<JSFlatString*> str(cx, IdToString(cx, id));
1345     if (!str)
1346         return false;
1347     return SuppressDeletedPropertyHelper(cx, obj, SingleStringPredicate(str));
1348 }
1349 
1350 bool
SuppressDeletedElement(JSContext * cx,HandleObject obj,uint32_t index)1351 js::SuppressDeletedElement(JSContext* cx, HandleObject obj, uint32_t index)
1352 {
1353     RootedId id(cx);
1354     if (!IndexToId(cx, index, &id))
1355         return false;
1356     return SuppressDeletedProperty(cx, obj, id);
1357 }
1358 
1359 bool
IteratorMore(JSContext * cx,HandleObject iterobj,MutableHandleValue rval)1360 js::IteratorMore(JSContext* cx, HandleObject iterobj, MutableHandleValue rval)
1361 {
1362     // Fast path for native iterators.
1363     if (iterobj->is<PropertyIteratorObject>()) {
1364         NativeIterator* ni = iterobj->as<PropertyIteratorObject>().getNativeIterator();
1365         bool done;
1366         if (!NativeIteratorNext(cx, ni, rval, &done))
1367             return false;
1368 
1369         if (done)
1370             rval.setMagic(JS_NO_ITER_VALUE);
1371         return true;
1372     }
1373 
1374     // We're reentering below and can call anything.
1375     JS_CHECK_RECURSION(cx, return false);
1376 
1377     // Call the iterator object's .next method.
1378     if (!GetProperty(cx, iterobj, iterobj, cx->names().next, rval))
1379         return false;
1380     // We try to support the old and new iterator protocol at the same time!
1381     if (!Invoke(cx, ObjectValue(*iterobj), rval, 0, nullptr, rval)) {
1382         // We still check for StopIterator
1383         if (!cx->isExceptionPending())
1384             return false;
1385         RootedValue exception(cx);
1386         if (!cx->getPendingException(&exception))
1387             return false;
1388         if (!JS_IsStopIteration(exception))
1389             return false;
1390 
1391         cx->clearPendingException();
1392         rval.setMagic(JS_NO_ITER_VALUE);
1393         return true;
1394     }
1395 
1396     if (!rval.isObject()) {
1397         // Old style generators might return primitive values
1398         return true;
1399     }
1400 
1401     // If the object has both the done and value property, we assume
1402     // it's using the new style protocol. Otherwise just return the object.
1403     RootedObject result(cx, &rval.toObject());
1404     bool found = false;
1405     if (!HasProperty(cx, result, cx->names().done, &found))
1406         return false;
1407     if (!found)
1408         return true;
1409     if (!HasProperty(cx, result, cx->names().value, &found))
1410         return false;
1411     if (!found)
1412         return true;
1413 
1414     // At this point we hopefully have a new style iterator result
1415 
1416     // 7.4.4 IteratorComplete
1417     // Get iterResult.done
1418     if (!GetProperty(cx, result, result, cx->names().done, rval))
1419         return false;
1420 
1421     bool done = ToBoolean(rval);
1422     if (done) {
1423          rval.setMagic(JS_NO_ITER_VALUE);
1424          return true;
1425      }
1426 
1427     // 7.4.5 IteratorValue
1428     return GetProperty(cx, result, result, cx->names().value, rval);
1429 }
1430 
1431 static bool
stopiter_hasInstance(JSContext * cx,HandleObject obj,MutableHandleValue v,bool * bp)1432 stopiter_hasInstance(JSContext* cx, HandleObject obj, MutableHandleValue v, bool* bp)
1433 {
1434     *bp = JS_IsStopIteration(v);
1435     return true;
1436 }
1437 
1438 const Class StopIterationObject::class_ = {
1439     "StopIteration",
1440     JSCLASS_HAS_CACHED_PROTO(JSProto_StopIteration),
1441     nullptr, /* addProperty */
1442     nullptr, /* delProperty */
1443     nullptr, /* getProperty */
1444     nullptr, /* setProperty */
1445     nullptr, /* enumerate */
1446     nullptr, /* resolve */
1447     nullptr, /* mayResolve */
1448     nullptr, /* finalize */
1449     nullptr, /* call */
1450     stopiter_hasInstance
1451 };
1452 
1453 static const JSFunctionSpec iterator_proto_methods[] = {
1454     JS_SELF_HOSTED_SYM_FN(iterator, "IteratorIdentity", 0, 0),
1455     JS_FS_END
1456 };
1457 
1458 /* static */ bool
initIteratorProto(JSContext * cx,Handle<GlobalObject * > global)1459 GlobalObject::initIteratorProto(JSContext* cx, Handle<GlobalObject*> global)
1460 {
1461     if (global->getReservedSlot(ITERATOR_PROTO).isObject())
1462         return true;
1463 
1464     RootedObject proto(cx, global->createBlankPrototype<PlainObject>(cx));
1465     if (!proto || !DefinePropertiesAndFunctions(cx, proto, nullptr, iterator_proto_methods))
1466         return false;
1467 
1468     global->setReservedSlot(ITERATOR_PROTO, ObjectValue(*proto));
1469     return true;
1470 }
1471 
1472 /* static */ bool
initArrayIteratorProto(JSContext * cx,Handle<GlobalObject * > global)1473 GlobalObject::initArrayIteratorProto(JSContext* cx, Handle<GlobalObject*> global)
1474 {
1475     if (global->getReservedSlot(ARRAY_ITERATOR_PROTO).isObject())
1476         return true;
1477 
1478     RootedObject iteratorProto(cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
1479     if (!iteratorProto)
1480         return false;
1481 
1482     const Class* cls = &ArrayIteratorPrototypeClass;
1483     RootedObject proto(cx, global->createBlankPrototypeInheriting(cx, cls, iteratorProto));
1484     if (!proto || !DefinePropertiesAndFunctions(cx, proto, nullptr, array_iterator_methods))
1485         return false;
1486 
1487     global->setReservedSlot(ARRAY_ITERATOR_PROTO, ObjectValue(*proto));
1488     return true;
1489 }
1490 
1491 /* static */ bool
initStringIteratorProto(JSContext * cx,Handle<GlobalObject * > global)1492 GlobalObject::initStringIteratorProto(JSContext* cx, Handle<GlobalObject*> global)
1493 {
1494     if (global->getReservedSlot(STRING_ITERATOR_PROTO).isObject())
1495         return true;
1496 
1497     RootedObject iteratorProto(cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
1498     if (!iteratorProto)
1499         return false;
1500 
1501     const Class* cls = &StringIteratorPrototypeClass;
1502     RootedObject proto(cx, global->createBlankPrototypeInheriting(cx, cls, iteratorProto));
1503     if (!proto || !DefinePropertiesAndFunctions(cx, proto, nullptr, string_iterator_methods))
1504         return false;
1505 
1506     global->setReservedSlot(STRING_ITERATOR_PROTO, ObjectValue(*proto));
1507     return true;
1508 }
1509 
1510 JSObject*
InitLegacyIteratorClass(JSContext * cx,HandleObject obj)1511 js::InitLegacyIteratorClass(JSContext* cx, HandleObject obj)
1512 {
1513     Handle<GlobalObject*> global = obj.as<GlobalObject>();
1514 
1515     if (global->getPrototype(JSProto_Iterator).isObject())
1516         return &global->getPrototype(JSProto_Iterator).toObject();
1517 
1518     RootedObject iteratorProto(cx);
1519     iteratorProto = global->createBlankPrototype(cx, &PropertyIteratorObject::class_);
1520     if (!iteratorProto)
1521         return nullptr;
1522 
1523     AutoIdVector blank(cx);
1524     NativeIterator* ni = NativeIterator::allocateIterator(cx, 0, blank);
1525     if (!ni)
1526         return nullptr;
1527     ni->init(nullptr, nullptr, 0 /* flags */, 0, 0);
1528 
1529     iteratorProto->as<PropertyIteratorObject>().setNativeIterator(ni);
1530 
1531     Rooted<JSFunction*> ctor(cx);
1532     ctor = global->createConstructor(cx, IteratorConstructor, cx->names().Iterator, 2);
1533     if (!ctor)
1534         return nullptr;
1535     if (!LinkConstructorAndPrototype(cx, ctor, iteratorProto))
1536         return nullptr;
1537     if (!DefinePropertiesAndFunctions(cx, iteratorProto, nullptr, legacy_iterator_methods))
1538         return nullptr;
1539     if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_Iterator,
1540                                               ctor, iteratorProto))
1541     {
1542         return nullptr;
1543     }
1544 
1545     return &global->getPrototype(JSProto_Iterator).toObject();
1546 }
1547 
1548 JSObject*
InitStopIterationClass(JSContext * cx,HandleObject obj)1549 js::InitStopIterationClass(JSContext* cx, HandleObject obj)
1550 {
1551     Handle<GlobalObject*> global = obj.as<GlobalObject>();
1552     if (!global->getPrototype(JSProto_StopIteration).isObject()) {
1553         RootedObject proto(cx, global->createBlankPrototype(cx, &StopIterationObject::class_));
1554         if (!proto || !FreezeObject(cx, proto))
1555             return nullptr;
1556 
1557         // This should use a non-JSProtoKey'd slot, but this is easier for now.
1558         if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_StopIteration, proto, proto))
1559             return nullptr;
1560 
1561         global->setConstructor(JSProto_StopIteration, ObjectValue(*proto));
1562     }
1563 
1564     return &global->getPrototype(JSProto_StopIteration).toObject();
1565 }
1566