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