Line data Source code
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 "vm/Iteration.h"
10 :
11 : #include "mozilla/DebugOnly.h"
12 : #include "mozilla/Likely.h"
13 : #include "mozilla/Maybe.h"
14 : #include "mozilla/MemoryReporting.h"
15 : #include "mozilla/PodOperations.h"
16 : #include "mozilla/Unused.h"
17 :
18 : #include <algorithm>
19 : #include <new>
20 :
21 : #include "jstypes.h"
22 : #include "jsutil.h"
23 :
24 : #include "builtin/Array.h"
25 : #include "ds/Sort.h"
26 : #include "gc/FreeOp.h"
27 : #include "gc/Marking.h"
28 : #include "js/Proxy.h"
29 : #include "vm/BytecodeUtil.h"
30 : #include "vm/GeneratorObject.h"
31 : #include "vm/GlobalObject.h"
32 : #include "vm/Interpreter.h"
33 : #include "vm/JSAtom.h"
34 : #include "vm/JSContext.h"
35 : #include "vm/JSObject.h"
36 : #include "vm/JSScript.h"
37 : #include "vm/Shape.h"
38 : #include "vm/TypedArrayObject.h"
39 :
40 : #include "vm/Compartment-inl.h"
41 : #include "vm/JSScript-inl.h"
42 : #include "vm/NativeObject-inl.h"
43 : #include "vm/ReceiverGuard-inl.h"
44 : #include "vm/Stack-inl.h"
45 : #include "vm/StringType-inl.h"
46 :
47 : using namespace js;
48 : using namespace js::gc;
49 :
50 : using mozilla::DebugOnly;
51 : using mozilla::Maybe;
52 : using mozilla::PodCopy;
53 : using mozilla::PodEqual;
54 :
55 : typedef Rooted<PropertyIteratorObject*> RootedPropertyIteratorObject;
56 :
57 : static const gc::AllocKind ITERATOR_FINALIZE_KIND = gc::AllocKind::OBJECT2_BACKGROUND;
58 :
59 : void
60 3 : NativeIterator::trace(JSTracer* trc)
61 : {
62 6 : TraceNullableEdge(trc, &objectBeingIterated_, "objectBeingIterated_");
63 :
64 : // The SuppressDeletedPropertyHelper loop can GC, so make sure that if the
65 : // GC removes any elements from the list, it won't remove this one.
66 0 : if (iterObj_)
67 3 : TraceManuallyBarrieredEdge(trc, &iterObj_, "iterObj");
68 :
69 : std::for_each(guardsBegin(), guardsEnd(),
70 : [trc](HeapReceiverGuard& guard) {
71 0 : guard.trace(trc);
72 9 : });
73 :
74 6 : GCPtrFlatString* begin = MOZ_LIKELY(isInitialized()) ? propertiesBegin() : propertyCursor_;
75 : std::for_each(begin, propertiesEnd(),
76 24 : [trc](GCPtrFlatString& prop) {
77 : // Properties begin life non-null and never *become*
78 : // null. (Deletion-suppression will shift trailing
79 : // properties over a deleted property in the properties
80 : // array, but it doesn't null them out.)
81 0 : TraceEdge(trc, &prop, "prop");
82 0 : });
83 3 : }
84 :
85 : typedef HashSet<jsid, DefaultHasher<jsid>> IdSet;
86 :
87 : template <bool CheckForDuplicates>
88 : static inline bool
89 18055 : Enumerate(JSContext* cx, HandleObject pobj, jsid id,
90 : bool enumerable, unsigned flags, Maybe<IdSet>& ht, AutoIdVector* props)
91 : {
92 : if (CheckForDuplicates) {
93 0 : if (!ht) {
94 95 : ht.emplace(cx);
95 : // Most of the time there are only a handful of entries.
96 0 : if (!ht->init(5))
97 5 : return false;
98 : }
99 :
100 : // If we've already seen this, we definitely won't add it.
101 0 : IdSet::AddPtr p = ht->lookupForAdd(id);
102 1870 : if (MOZ_UNLIKELY(!!p))
103 : return true;
104 :
105 : // It's not necessary to add properties to the hash table at the end of
106 : // the prototype chain, but custom enumeration behaviors might return
107 : // duplicated properties, so always add in such cases.
108 0 : if (pobj->is<ProxyObject>() ||
109 0 : pobj->staticPrototype() ||
110 2468 : pobj->getClass()->getNewEnumerate())
111 : {
112 1262 : if (!ht->add(p, id))
113 : return false;
114 : }
115 : }
116 :
117 19286 : if (!enumerable && !(flags & JSITER_HIDDEN))
118 : return true;
119 :
120 : // Symbol-keyed properties and nonenumerable properties are skipped unless
121 : // the caller specifically asks for them. A caller can also filter out
122 : // non-symbols by asking for JSITER_SYMBOLSONLY.
123 : if (JSID_IS_SYMBOL(id) ? !(flags & JSITER_SYMBOLS) : (flags & JSITER_SYMBOLSONLY))
124 : return true;
125 :
126 18567 : return props->append(id);
127 : }
128 :
129 : template <bool CheckForDuplicates>
130 : static bool
131 180 : EnumerateExtraProperties(JSContext* cx, HandleObject obj, unsigned flags, Maybe<IdSet>& ht,
132 : AutoIdVector* props)
133 : {
134 540 : MOZ_ASSERT(obj->getClass()->getNewEnumerate());
135 :
136 0 : AutoIdVector properties(cx);
137 0 : bool enumerableOnly = !(flags & JSITER_HIDDEN);
138 540 : if (!obj->getClass()->getNewEnumerate()(cx, obj, properties, enumerableOnly))
139 : return false;
140 :
141 0 : RootedId id(cx);
142 0 : for (size_t n = 0; n < properties.length(); n++) {
143 1164 : id = properties[n];
144 :
145 : // The enumerate hook does not indicate whether the properties
146 : // it returns are enumerable or not. Since we already passed
147 : // `enumerableOnly` to the hook to filter out non-enumerable
148 : // properties, it doesn't really matter what we pass here.
149 0 : bool enumerable = true;
150 776 : if (!Enumerate<CheckForDuplicates>(cx, obj, id, enumerable, flags, ht, props))
151 : return false;
152 : }
153 :
154 : return true;
155 : }
156 :
157 : static bool
158 0 : SortComparatorIntegerIds(jsid a, jsid b, bool* lessOrEqualp)
159 : {
160 : uint32_t indexA, indexB;
161 0 : MOZ_ALWAYS_TRUE(IdIsIndex(a, &indexA));
162 0 : MOZ_ALWAYS_TRUE(IdIsIndex(b, &indexB));
163 0 : *lessOrEqualp = (indexA <= indexB);
164 0 : return true;
165 : }
166 :
167 : template <bool CheckForDuplicates>
168 : static bool
169 6529 : EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags, Maybe<IdSet>& ht,
170 : AutoIdVector* props, Handle<UnboxedPlainObject*> unboxed = nullptr)
171 : {
172 : bool enumerateSymbols;
173 6529 : if (flags & JSITER_SYMBOLSONLY) {
174 : enumerateSymbols = true;
175 : } else {
176 : /* Collect any dense elements from this object. */
177 0 : size_t firstElemIndex = props->length();
178 0 : size_t initlen = pobj->getDenseInitializedLength();
179 0 : const Value* vp = pobj->getDenseElements();
180 0 : bool hasHoles = false;
181 0 : for (size_t i = 0; i < initlen; ++i, ++vp) {
182 2688 : if (vp->isMagic(JS_ELEMENTS_HOLE)) {
183 : hasHoles = true;
184 : } else {
185 : /* Dense arrays never get so large that i would not fit into an integer id. */
186 7776 : if (!Enumerate<CheckForDuplicates>(cx, pobj, INT_TO_JSID(i),
187 : /* enumerable = */ true, flags, ht, props))
188 : {
189 0 : return false;
190 : }
191 : }
192 : }
193 :
194 : /* Collect any typed array or shared typed array elements from this object. */
195 0 : if (pobj->is<TypedArrayObject>()) {
196 0 : size_t len = pobj->as<TypedArrayObject>().length();
197 0 : for (size_t i = 0; i < len; i++) {
198 0 : if (!Enumerate<CheckForDuplicates>(cx, pobj, INT_TO_JSID(i),
199 : /* enumerable = */ true, flags, ht, props))
200 : {
201 : return false;
202 : }
203 : }
204 : }
205 :
206 : // Collect any sparse elements from this object.
207 0 : bool isIndexed = pobj->isIndexed();
208 6529 : if (isIndexed) {
209 : // If the dense elements didn't have holes, we don't need to include
210 : // them in the sort.
211 0 : if (!hasHoles)
212 0 : firstElemIndex = props->length();
213 :
214 0 : for (Shape::Range<NoGC> r(pobj->lastProperty()); !r.empty(); r.popFront()) {
215 0 : Shape& shape = r.front();
216 0 : jsid id = shape.propid();
217 : uint32_t dummy;
218 0 : if (IdIsIndex(id, &dummy)) {
219 0 : if (!Enumerate<CheckForDuplicates>(cx, pobj, id, shape.enumerable(), flags, ht,
220 : props))
221 : {
222 0 : return false;
223 : }
224 : }
225 : }
226 :
227 0 : MOZ_ASSERT(firstElemIndex <= props->length());
228 :
229 0 : jsid* ids = props->begin() + firstElemIndex;
230 0 : size_t n = props->length() - firstElemIndex;
231 :
232 0 : AutoIdVector tmp(cx);
233 0 : if (!tmp.resize(n))
234 : return false;
235 0 : PodCopy(tmp.begin(), ids, n);
236 :
237 0 : if (!MergeSort(ids, n, tmp.begin(), SortComparatorIntegerIds))
238 : return false;
239 : }
240 :
241 6529 : if (unboxed) {
242 : // If |unboxed| is set then |pobj| is the expando for an unboxed
243 : // plain object we are enumerating. Add the unboxed properties
244 : // themselves here since they are all property names that were
245 : // given to the object before any of the expando's properties.
246 0 : MOZ_ASSERT(pobj->is<UnboxedExpandoObject>());
247 33 : if (!EnumerateExtraProperties<CheckForDuplicates>(cx, unboxed, flags, ht, props))
248 : return false;
249 : }
250 :
251 6529 : size_t initialLength = props->length();
252 :
253 : /* Collect all unique property names from this object's shape. */
254 0 : bool symbolsFound = false;
255 0 : Shape::Range<NoGC> r(pobj->lastProperty());
256 0 : for (; !r.empty(); r.popFront()) {
257 0 : Shape& shape = r.front();
258 34972 : jsid id = shape.propid();
259 :
260 17486 : if (JSID_IS_SYMBOL(id)) {
261 : symbolsFound = true;
262 1 : continue;
263 : }
264 :
265 : uint32_t dummy;
266 17485 : if (isIndexed && IdIsIndex(id, &dummy))
267 : continue;
268 :
269 0 : if (!Enumerate<CheckForDuplicates>(cx, pobj, id, shape.enumerable(), flags, ht, props))
270 0 : return false;
271 : }
272 19587 : ::Reverse(props->begin() + initialLength, props->end());
273 :
274 6529 : enumerateSymbols = symbolsFound && (flags & JSITER_SYMBOLS);
275 : }
276 :
277 6529 : if (enumerateSymbols) {
278 : // Do a second pass to collect symbols. ES6 draft rev 25 (2014 May 22)
279 : // 9.1.12 requires that all symbols appear after all strings in the
280 : // result.
281 0 : size_t initialLength = props->length();
282 0 : for (Shape::Range<NoGC> r(pobj->lastProperty()); !r.empty(); r.popFront()) {
283 0 : Shape& shape = r.front();
284 0 : jsid id = shape.propid();
285 0 : if (JSID_IS_SYMBOL(id)) {
286 0 : if (!Enumerate<CheckForDuplicates>(cx, pobj, id, shape.enumerable(), flags, ht,
287 : props))
288 : {
289 0 : return false;
290 : }
291 : }
292 : }
293 0 : ::Reverse(props->begin() + initialLength, props->end());
294 : }
295 :
296 : return true;
297 : }
298 :
299 : static bool
300 6529 : EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags, Maybe<IdSet>& ht,
301 : AutoIdVector* props, bool checkForDuplicates,
302 : Handle<UnboxedPlainObject*> unboxed = nullptr)
303 : {
304 0 : if (checkForDuplicates)
305 0 : return EnumerateNativeProperties<true>(cx, pobj, flags, ht, props, unboxed);
306 6334 : return EnumerateNativeProperties<false>(cx, pobj, flags, ht, props, unboxed);
307 : }
308 :
309 : template <bool CheckForDuplicates>
310 : static bool
311 192 : EnumerateProxyProperties(JSContext* cx, HandleObject pobj, unsigned flags, Maybe<IdSet>& ht,
312 : AutoIdVector* props)
313 : {
314 384 : MOZ_ASSERT(pobj->is<ProxyObject>());
315 :
316 384 : AutoIdVector proxyProps(cx);
317 :
318 192 : if (flags & JSITER_HIDDEN || flags & JSITER_SYMBOLS) {
319 : // This gets all property keys, both strings and symbols. The call to
320 : // Enumerate in the loop below will filter out unwanted keys, per the
321 : // flags.
322 24 : if (!Proxy::ownPropertyKeys(cx, pobj, proxyProps))
323 : return false;
324 :
325 0 : Rooted<PropertyDescriptor> desc(cx);
326 0 : for (size_t n = 0, len = proxyProps.length(); n < len; n++) {
327 182 : bool enumerable = false;
328 :
329 : // We need to filter, if the caller just wants enumerable symbols.
330 0 : if (!(flags & JSITER_HIDDEN)) {
331 0 : if (!Proxy::getOwnPropertyDescriptor(cx, pobj, proxyProps[n], &desc))
332 : return false;
333 0 : enumerable = desc.enumerable();
334 : }
335 :
336 546 : if (!Enumerate<CheckForDuplicates>(cx, pobj, proxyProps[n], enumerable, flags, ht,
337 : props))
338 : {
339 : return false;
340 : }
341 : }
342 :
343 : return true;
344 : }
345 :
346 : // Returns enumerable property names (no symbols).
347 168 : if (!Proxy::getOwnEnumerablePropertyKeys(cx, pobj, proxyProps))
348 : return false;
349 :
350 0 : for (size_t n = 0, len = proxyProps.length(); n < len; n++) {
351 1608 : if (!Enumerate<CheckForDuplicates>(cx, pobj, proxyProps[n], true, flags, ht, props))
352 : return false;
353 : }
354 :
355 : return true;
356 : }
357 :
358 : #ifdef JS_MORE_DETERMINISTIC
359 :
360 : struct SortComparatorIds
361 : {
362 : JSContext* const cx;
363 :
364 : SortComparatorIds(JSContext* cx)
365 : : cx(cx) {}
366 :
367 : bool operator()(jsid a, jsid b, bool* lessOrEqualp)
368 : {
369 : // Pick an arbitrary order on jsids that is as stable as possible
370 : // across executions.
371 : if (a == b) {
372 : *lessOrEqualp = true;
373 : return true;
374 : }
375 :
376 : size_t ta = JSID_BITS(a) & JSID_TYPE_MASK;
377 : size_t tb = JSID_BITS(b) & JSID_TYPE_MASK;
378 : if (ta != tb) {
379 : *lessOrEqualp = (ta <= tb);
380 : return true;
381 : }
382 :
383 : if (JSID_IS_INT(a)) {
384 : *lessOrEqualp = (JSID_TO_INT(a) <= JSID_TO_INT(b));
385 : return true;
386 : }
387 :
388 : RootedString astr(cx), bstr(cx);
389 : if (JSID_IS_SYMBOL(a)) {
390 : MOZ_ASSERT(JSID_IS_SYMBOL(b));
391 : JS::SymbolCode ca = JSID_TO_SYMBOL(a)->code();
392 : JS::SymbolCode cb = JSID_TO_SYMBOL(b)->code();
393 : if (ca != cb) {
394 : *lessOrEqualp = uint32_t(ca) <= uint32_t(cb);
395 : return true;
396 : }
397 : MOZ_ASSERT(ca == JS::SymbolCode::InSymbolRegistry || ca == JS::SymbolCode::UniqueSymbol);
398 : astr = JSID_TO_SYMBOL(a)->description();
399 : bstr = JSID_TO_SYMBOL(b)->description();
400 : if (!astr || !bstr) {
401 : *lessOrEqualp = !astr;
402 : return true;
403 : }
404 :
405 : // Fall through to string comparison on the descriptions. The sort
406 : // order is nondeterministic if two different unique symbols have
407 : // the same description.
408 : } else {
409 : astr = IdToString(cx, a);
410 : if (!astr)
411 : return false;
412 : bstr = IdToString(cx, b);
413 : if (!bstr)
414 : return false;
415 : }
416 :
417 : int32_t result;
418 : if (!CompareStrings(cx, astr, bstr, &result))
419 : return false;
420 :
421 : *lessOrEqualp = (result <= 0);
422 : return true;
423 : }
424 : };
425 :
426 : #endif /* JS_MORE_DETERMINISTIC */
427 :
428 : static bool
429 6768 : Snapshot(JSContext* cx, HandleObject pobj_, unsigned flags, AutoIdVector* props)
430 : {
431 : // We initialize |ht| lazily (in Enumerate()) because it ends up unused
432 : // anywhere from 67--99.9% of the time.
433 0 : Maybe<IdSet> ht;
434 13536 : RootedObject pobj(cx, pobj_);
435 :
436 : // Don't check for duplicates if we're only interested in own properties.
437 : // This does the right thing for most objects: native objects don't have
438 : // duplicate property ids and we allow the [[OwnPropertyKeys]] proxy trap to
439 : // return duplicates.
440 : //
441 : // The only special case is when the object has a newEnumerate hook: it
442 : // can return duplicate properties and we have to filter them. This is
443 : // handled below.
444 6768 : bool checkForDuplicates = !(flags & JSITER_OWNONLY);
445 :
446 0 : do {
447 0 : if (pobj->getClass()->getNewEnumerate()) {
448 540 : if (pobj->is<UnboxedPlainObject>() && pobj->as<UnboxedPlainObject>().maybeExpando()) {
449 : // Special case unboxed objects with an expando object.
450 0 : RootedNativeObject expando(cx, pobj->as<UnboxedPlainObject>().maybeExpando());
451 66 : if (!EnumerateNativeProperties(cx, expando, flags, ht, props, checkForDuplicates,
452 : pobj.as<UnboxedPlainObject>()))
453 : {
454 0 : return false;
455 : }
456 : } else {
457 : // The newEnumerate hook may return duplicates. Whitelist the
458 : // unboxed object hooks because we know they are well-behaved.
459 0 : if (!pobj->is<UnboxedPlainObject>())
460 0 : checkForDuplicates = true;
461 :
462 0 : if (checkForDuplicates) {
463 0 : if (!EnumerateExtraProperties<true>(cx, pobj, flags, ht, props))
464 : return false;
465 : } else {
466 147 : if (!EnumerateExtraProperties<false>(cx, pobj, flags, ht, props))
467 : return false;
468 : }
469 :
470 0 : if (pobj->isNative()) {
471 0 : if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, ht, props,
472 : checkForDuplicates))
473 : {
474 : return false;
475 : }
476 : }
477 : }
478 13376 : } else if (pobj->isNative()) {
479 : // Give the object a chance to resolve all lazy properties
480 0 : if (JSEnumerateOp enumerate = pobj->getClass()->getEnumerate()) {
481 14 : if (!enumerate(cx, pobj.as<NativeObject>()))
482 : return false;
483 : }
484 6496 : if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, ht, props,
485 : checkForDuplicates))
486 : {
487 : return false;
488 : }
489 0 : } else if (pobj->is<ProxyObject>()) {
490 0 : if (checkForDuplicates) {
491 0 : if (!EnumerateProxyProperties<true>(cx, pobj, flags, ht, props))
492 : return false;
493 : } else {
494 192 : if (!EnumerateProxyProperties<false>(cx, pobj, flags, ht, props))
495 : return false;
496 : }
497 : } else {
498 0 : MOZ_CRASH("non-native objects must have an enumerate op");
499 : }
500 :
501 6868 : if (flags & JSITER_OWNONLY)
502 : break;
503 :
504 390 : if (!GetPrototype(cx, pobj, &pobj))
505 : return false;
506 :
507 : } while (pobj != nullptr);
508 :
509 : #ifdef JS_MORE_DETERMINISTIC
510 :
511 : /*
512 : * In some cases the enumeration order for an object depends on the
513 : * execution mode (interpreter vs. JIT), especially for native objects
514 : * with a class enumerate hook (where resolving a property changes the
515 : * resulting enumeration order). These aren't really bugs, but the
516 : * differences can change the generated output and confuse correctness
517 : * fuzzers, so we sort the ids if such a fuzzer is running.
518 : *
519 : * We don't do this in the general case because (a) doing so is slow,
520 : * and (b) it also breaks the web, which expects enumeration order to
521 : * follow the order in which properties are added, in certain cases.
522 : * Since ECMA does not specify an enumeration order for objects, both
523 : * behaviors are technically correct to do.
524 : */
525 :
526 : jsid* ids = props->begin();
527 : size_t n = props->length();
528 :
529 : AutoIdVector tmp(cx);
530 : if (!tmp.resize(n))
531 : return false;
532 : PodCopy(tmp.begin(), ids, n);
533 :
534 : if (!MergeSort(ids, n, tmp.begin(), SortComparatorIds(cx)))
535 : return false;
536 :
537 : #endif /* JS_MORE_DETERMINISTIC */
538 :
539 : return true;
540 : }
541 :
542 : JS_FRIEND_API(bool)
543 6663 : js::GetPropertyKeys(JSContext* cx, HandleObject obj, unsigned flags, AutoIdVector* props)
544 : {
545 6663 : return Snapshot(cx, obj,
546 : flags & (JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | JSITER_SYMBOLSONLY),
547 6673 : props);
548 : }
549 :
550 : static inline void
551 171 : RegisterEnumerator(ObjectRealm& realm, NativeIterator* ni)
552 : {
553 : /* Register non-escaping native enumerators (for-in) with the current context. */
554 171 : ni->link(realm.enumerators);
555 :
556 0 : MOZ_ASSERT(!ni->isActive());
557 0 : ni->markActive();
558 171 : }
559 :
560 : static PropertyIteratorObject*
561 124 : NewPropertyIteratorObject(JSContext* cx)
562 : {
563 0 : RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &PropertyIteratorObject::class_,
564 0 : TaggedProto(nullptr)));
565 124 : if (!group)
566 : return nullptr;
567 :
568 0 : const Class* clasp = &PropertyIteratorObject::class_;
569 0 : RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(nullptr),
570 0 : ITERATOR_FINALIZE_KIND));
571 124 : if (!shape)
572 : return nullptr;
573 :
574 : JSObject* obj;
575 372 : JS_TRY_VAR_OR_RETURN_NULL(cx, obj, NativeObject::create(cx, ITERATOR_FINALIZE_KIND,
576 : GetInitialHeap(GenericObject, clasp),
577 : shape, group));
578 :
579 124 : PropertyIteratorObject* res = &obj->as<PropertyIteratorObject>();
580 :
581 : // CodeGenerator::visitIteratorStartO assumes the iterator object is not
582 : // inside the nursery when deciding whether a barrier is necessary.
583 124 : MOZ_ASSERT(!js::gc::IsInsideNursery(res));
584 :
585 248 : MOZ_ASSERT(res->numFixedSlots() == JSObject::ITER_CLASS_NFIXED_SLOTS);
586 : return res;
587 : }
588 :
589 : static PropertyIteratorObject*
590 124 : CreatePropertyIterator(JSContext* cx, Handle<JSObject*> objBeingIterated,
591 : const AutoIdVector& props, uint32_t numGuards, uint32_t guardKey)
592 : {
593 0 : Rooted<PropertyIteratorObject*> propIter(cx, NewPropertyIteratorObject(cx));
594 124 : if (!propIter)
595 : return nullptr;
596 :
597 : static_assert(sizeof(ReceiverGuard) == 2 * sizeof(GCPtrFlatString),
598 : "NativeIterators are allocated in space for 1) themselves, "
599 : "2) the properties a NativeIterator iterates (as "
600 : "GCPtrFlatStrings), and 3) |numGuards| HeapReceiverGuard "
601 : "objects; the additional-length calculation below assumes "
602 : "this size-relationship when determining the extra space to "
603 : "allocate");
604 :
605 124 : size_t extraCount = props.length() + numGuards * 2;
606 : void* mem =
607 0 : cx->zone()->pod_malloc_with_extra<NativeIterator, GCPtrFlatString>(extraCount);
608 0 : if (!mem) {
609 0 : ReportOutOfMemory(cx);
610 0 : return nullptr;
611 : }
612 :
613 : // This also registers |ni| with |propIter|.
614 124 : bool hadError = false;
615 : NativeIterator* ni =
616 : new (mem) NativeIterator(cx, propIter, objBeingIterated, props, numGuards, guardKey,
617 0 : &hadError);
618 124 : if (hadError)
619 : return nullptr;
620 :
621 : ObjectRealm& realm =
622 0 : objBeingIterated ? ObjectRealm::get(objBeingIterated) : ObjectRealm::get(propIter);
623 124 : RegisterEnumerator(realm, ni);
624 :
625 124 : return propIter;
626 : }
627 :
628 : /**
629 : * Initialize a sentinel NativeIterator whose purpose is only to act as the
630 : * start/end of the circular linked list of NativeIterators in
631 : * ObjectRealm::enumerators.
632 : */
633 94 : NativeIterator::NativeIterator()
634 : {
635 : // Do our best to enforce that nothing in |this| except the two fields set
636 : // below is ever observed.
637 47 : JS_POISON(static_cast<void*>(this), 0xCC, sizeof(*this), MemCheckKind::MakeUndefined);
638 :
639 : // These are the only two fields in sentinel NativeIterators that are
640 : // examined, in ObjectRealm::sweepNativeIterators. Everything else is
641 : // only examined *if* it's a NativeIterator being traced by a
642 : // PropertyIteratorObject that owns it, and nothing owns this iterator.
643 1 : prev_ = next_ = this;
644 0 : }
645 :
646 : NativeIterator*
647 47 : NativeIterator::allocateSentinel(JSContext* maybecx)
648 : {
649 0 : NativeIterator* ni = js_new<NativeIterator>();
650 0 : if (!ni) {
651 0 : if (maybecx)
652 0 : ReportOutOfMemory(maybecx);
653 : }
654 :
655 47 : return ni;
656 : }
657 :
658 : /**
659 : * Initialize a fresh NativeIterator.
660 : *
661 : * This definition is a bit tricky: some parts of initializing are fallible, so
662 : * as we initialize, we must carefully keep this in GC-safe state (see
663 : * NativeIterator::trace).
664 : */
665 124 : NativeIterator::NativeIterator(JSContext* cx, Handle<PropertyIteratorObject*> propIter,
666 : Handle<JSObject*> objBeingIterated, const AutoIdVector& props,
667 124 : uint32_t numGuards, uint32_t guardKey, bool* hadError)
668 : : objectBeingIterated_(objBeingIterated),
669 : iterObj_(propIter),
670 : // NativeIterator initially acts (before full initialization) as if it
671 : // contains no guards...
672 124 : guardsEnd_(guardsBegin()),
673 : // ...and no properties.
674 124 : propertyCursor_(reinterpret_cast<GCPtrFlatString*>(guardsBegin() + numGuards)),
675 : propertiesEnd_(propertyCursor_),
676 : guardKey_(guardKey),
677 620 : flags_(0)
678 : {
679 124 : MOZ_ASSERT(!*hadError);
680 :
681 : // NOTE: This must be done first thing: PropertyIteratorObject::finalize
682 : // can only free |this| (and not leak it) if this has happened.
683 248 : propIter->setNativeIterator(this);
684 :
685 0 : for (size_t i = 0, len = props.length(); i < len; i++) {
686 0 : JSFlatString* str = IdToString(cx, props[i]);
687 0 : if (!str) {
688 0 : *hadError = true;
689 0 : return;
690 : }
691 :
692 : // Placement-new the next property string at the end of the currently
693 : // computed property strings.
694 746 : GCPtrFlatString* loc = propertiesEnd_;
695 :
696 : // Increase the overall property string count before initializing the
697 : // property string, so this construction isn't on a location not known
698 : // to the GC yet.
699 746 : propertiesEnd_++;
700 :
701 1492 : new (loc) GCPtrFlatString(str);
702 : }
703 :
704 124 : if (numGuards > 0) {
705 : // Construct guards into the guard array. Also recompute the guard key,
706 : // which incorporates Shape* and ObjectGroup* addresses that could have
707 : // changed during a GC triggered in (among other places) |IdToString|
708 : //. above.
709 94 : JSObject* pobj = objBeingIterated;
710 : #ifdef DEBUG
711 94 : uint32_t i = 0;
712 : #endif
713 94 : uint32_t key = 0;
714 : do {
715 192 : ReceiverGuard guard(pobj);
716 :
717 : // Placement-new the next HeapReceiverGuard at the end of the
718 : // currently initialized HeapReceiverGuards.
719 192 : HeapReceiverGuard* loc = guardsEnd_;
720 :
721 : // Increase the overall guard-count before initializing the
722 : // HeapReceiverGuard, so this construction isn't on a location not
723 : // known to the GC.
724 192 : guardsEnd_++;
725 : #ifdef DEBUG
726 192 : i++;
727 : #endif
728 :
729 384 : new (loc) HeapReceiverGuard(guard);
730 :
731 384 : key = mozilla::AddToHash(key, guard.hash());
732 :
733 : // The one caller of this method that passes |numGuards > 0|, does
734 : // so only if the entire chain consists of cacheable objects (that
735 : // necessarily have static prototypes).
736 0 : pobj = pobj->staticPrototype();
737 192 : } while (pobj);
738 :
739 0 : guardKey_ = key;
740 94 : MOZ_ASSERT(i == numGuards);
741 : }
742 :
743 0 : MOZ_ASSERT(static_cast<void*>(guardsEnd_) == propertyCursor_);
744 124 : markInitialized();
745 :
746 124 : MOZ_ASSERT(!*hadError);
747 : }
748 :
749 : static inline PropertyIteratorObject*
750 119 : VectorToKeyIterator(JSContext* cx, HandleObject obj, AutoIdVector& props, uint32_t numGuards)
751 : {
752 270 : if (obj->isSingleton() && !JSObject::setIteratedSingleton(cx, obj))
753 : return nullptr;
754 119 : MarkObjectGroupFlags(cx, obj, OBJECT_FLAG_ITERATED);
755 :
756 119 : return CreatePropertyIterator(cx, obj, props, numGuards, 0);
757 : }
758 :
759 :
760 : JSObject*
761 24 : js::EnumeratedIdVectorToIterator(JSContext* cx, HandleObject obj, AutoIdVector& props)
762 : {
763 24 : return VectorToKeyIterator(cx, obj, props, 0);
764 : }
765 :
766 : // Mainly used for .. in over null/undefined
767 : JSObject*
768 5 : js::NewEmptyPropertyIterator(JSContext* cx)
769 : {
770 0 : AutoIdVector props(cx); // Empty
771 10 : return CreatePropertyIterator(cx, nullptr, props, 0, 0);
772 : }
773 :
774 : /* static */ bool
775 78 : IteratorHashPolicy::match(PropertyIteratorObject* obj, const Lookup& lookup)
776 : {
777 0 : NativeIterator* ni = obj->getNativeIterator();
778 156 : if (ni->guardKey() != lookup.key || ni->guardCount() != lookup.numGuards)
779 : return false;
780 :
781 0 : return PodEqual(reinterpret_cast<ReceiverGuard*>(ni->guardsBegin()), lookup.guards,
782 156 : ni->guardCount());
783 : }
784 :
785 : static inline bool
786 465 : CanCompareIterableObjectToCache(JSObject* obj)
787 : {
788 0 : if (obj->isNative())
789 0 : return obj->as<NativeObject>().hasEmptyElements();
790 0 : if (obj->is<UnboxedPlainObject>()) {
791 0 : if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
792 0 : return expando->hasEmptyElements();
793 : return true;
794 : }
795 : return false;
796 : }
797 :
798 : using ReceiverGuardVector = Vector<ReceiverGuard, 8>;
799 :
800 : static MOZ_ALWAYS_INLINE PropertyIteratorObject*
801 247 : LookupInIteratorCache(JSContext* cx, JSObject* obj, uint32_t* numGuards)
802 : {
803 247 : MOZ_ASSERT(*numGuards == 0);
804 :
805 0 : ReceiverGuardVector guards(cx);
806 0 : uint32_t key = 0;
807 247 : JSObject* pobj = obj;
808 : do {
809 0 : if (!CanCompareIterableObjectToCache(pobj))
810 44 : return nullptr;
811 :
812 0 : ReceiverGuard guard(pobj);
813 842 : key = mozilla::AddToHash(key, guard.hash());
814 :
815 1 : if (MOZ_UNLIKELY(!guards.append(guard))) {
816 0 : cx->recoverFromOutOfMemory();
817 0 : return nullptr;
818 : }
819 :
820 0 : pobj = pobj->staticPrototype();
821 421 : } while (pobj);
822 :
823 0 : MOZ_ASSERT(!guards.empty());
824 203 : *numGuards = guards.length();
825 :
826 0 : IteratorHashPolicy::Lookup lookup(guards.begin(), guards.length(), key);
827 0 : auto p = ObjectRealm::get(obj).iteratorCache.lookup(lookup);
828 203 : if (!p)
829 : return nullptr;
830 :
831 0 : PropertyIteratorObject* iterobj = *p;
832 228 : MOZ_ASSERT(iterobj->compartment() == cx->compartment());
833 :
834 0 : NativeIterator* ni = iterobj->getNativeIterator();
835 76 : if (!ni->isReusable())
836 : return nullptr;
837 :
838 75 : return iterobj;
839 : }
840 :
841 : static bool
842 189 : CanStoreInIteratorCache(JSObject* obj)
843 : {
844 : do {
845 0 : if (obj->isNative()) {
846 770 : MOZ_ASSERT(obj->as<NativeObject>().hasEmptyElements());
847 :
848 : // Typed arrays have indexed properties not captured by the Shape guard.
849 : // Enumerate hooks may add extra properties.
850 0 : const Class* clasp = obj->getClass();
851 385 : if (MOZ_UNLIKELY(IsTypedArrayClass(clasp)))
852 : return false;
853 770 : if (MOZ_UNLIKELY(clasp->getNewEnumerate() || clasp->getEnumerate()))
854 : return false;
855 : } else {
856 0 : MOZ_ASSERT(obj->is<UnboxedPlainObject>());
857 : }
858 :
859 0 : obj = obj->staticPrototype();
860 384 : } while (obj);
861 :
862 : return true;
863 : }
864 :
865 : static MOZ_MUST_USE bool
866 94 : StoreInIteratorCache(JSContext* cx, JSObject* obj, PropertyIteratorObject* iterobj)
867 : {
868 94 : MOZ_ASSERT(CanStoreInIteratorCache(obj));
869 :
870 0 : NativeIterator* ni = iterobj->getNativeIterator();
871 94 : MOZ_ASSERT(ni->guardCount() > 0);
872 :
873 0 : IteratorHashPolicy::Lookup lookup(reinterpret_cast<ReceiverGuard*>(ni->guardsBegin()),
874 0 : ni->guardCount(),
875 282 : ni->guardKey());
876 :
877 94 : ObjectRealm::IteratorCache& cache = ObjectRealm::get(obj).iteratorCache;
878 : bool ok;
879 0 : auto p = cache.lookupForAdd(lookup);
880 0 : if (MOZ_LIKELY(!p)) {
881 92 : ok = cache.add(p, iterobj);
882 : } else {
883 : // If we weren't able to use an existing cached iterator, just
884 : // replace it.
885 0 : cache.remove(p);
886 2 : ok = cache.relookupOrAdd(p, lookup, iterobj);
887 : }
888 1 : if (!ok) {
889 0 : ReportOutOfMemory(cx);
890 0 : return false;
891 : }
892 :
893 : return true;
894 : }
895 :
896 : JSObject*
897 166 : js::GetIterator(JSContext* cx, HandleObject obj)
898 : {
899 0 : uint32_t numGuards = 0;
900 0 : if (PropertyIteratorObject* iterobj = LookupInIteratorCache(cx, obj, &numGuards)) {
901 0 : NativeIterator* ni = iterobj->getNativeIterator();
902 0 : ni->changeObjectBeingIterated(*obj);
903 0 : RegisterEnumerator(ObjectRealm::get(obj), ni);
904 47 : return iterobj;
905 : }
906 :
907 0 : if (numGuards > 0 && !CanStoreInIteratorCache(obj))
908 1 : numGuards = 0;
909 :
910 238 : MOZ_ASSERT(!obj->is<PropertyIteratorObject>());
911 :
912 0 : if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
913 24 : return Proxy::enumerate(cx, obj);
914 :
915 0 : AutoIdVector keys(cx);
916 95 : if (!Snapshot(cx, obj, 0, &keys))
917 : return nullptr;
918 :
919 0 : JSObject* res = VectorToKeyIterator(cx, obj, keys, numGuards);
920 95 : if (!res)
921 : return nullptr;
922 :
923 0 : PropertyIteratorObject* iterobj = &res->as<PropertyIteratorObject>();
924 95 : assertSameCompartment(cx, iterobj);
925 :
926 : // Cache the iterator object.
927 0 : if (numGuards > 0) {
928 188 : if (!StoreInIteratorCache(cx, obj, iterobj))
929 : return nullptr;
930 : }
931 :
932 95 : return iterobj;
933 : }
934 :
935 : PropertyIteratorObject*
936 81 : js::LookupInIteratorCache(JSContext* cx, HandleObject obj)
937 : {
938 0 : uint32_t numGuards = 0;
939 81 : return LookupInIteratorCache(cx, obj, &numGuards);
940 : }
941 :
942 : // ES 2017 draft 7.4.7.
943 : JSObject*
944 0 : js::CreateIterResultObject(JSContext* cx, HandleValue value, bool done)
945 : {
946 : // Step 1 (implicit).
947 :
948 : // Step 2.
949 0 : RootedObject templateObject(cx, cx->realm()->getOrCreateIterResultTemplateObject(cx));
950 0 : if (!templateObject)
951 : return nullptr;
952 :
953 : NativeObject* resultObj;
954 0 : JS_TRY_VAR_OR_RETURN_NULL(cx, resultObj, NativeObject::createWithTemplate(cx, gc::DefaultHeap,
955 : templateObject));
956 :
957 : // Step 3.
958 0 : resultObj->setSlot(Realm::IterResultObjectValueSlot, value);
959 :
960 : // Step 4.
961 0 : resultObj->setSlot(Realm::IterResultObjectDoneSlot,
962 0 : done ? TrueHandleValue : FalseHandleValue);
963 :
964 : // Step 5.
965 0 : return resultObj;
966 : }
967 :
968 : NativeObject*
969 0 : Realm::getOrCreateIterResultTemplateObject(JSContext* cx)
970 : {
971 0 : MOZ_ASSERT(cx->realm() == this);
972 :
973 0 : if (iterResultTemplate_)
974 0 : return iterResultTemplate_;
975 :
976 : // Create template plain object
977 0 : RootedNativeObject templateObject(cx, NewBuiltinClassInstance<PlainObject>(cx, TenuredObject));
978 0 : if (!templateObject)
979 0 : return iterResultTemplate_; // = nullptr
980 :
981 : // Create a new group for the template.
982 0 : Rooted<TaggedProto> proto(cx, templateObject->taggedProto());
983 0 : RootedObjectGroup group(cx, ObjectGroupRealm::makeGroup(cx, templateObject->getClass(),
984 0 : proto));
985 0 : if (!group)
986 0 : return iterResultTemplate_; // = nullptr
987 0 : templateObject->setGroup(group);
988 :
989 : // Set dummy `value` property
990 0 : if (!NativeDefineDataProperty(cx, templateObject, cx->names().value, UndefinedHandleValue,
991 : JSPROP_ENUMERATE))
992 : {
993 0 : return iterResultTemplate_; // = nullptr
994 : }
995 :
996 : // Set dummy `done` property
997 0 : if (!NativeDefineDataProperty(cx, templateObject, cx->names().done, TrueHandleValue,
998 : JSPROP_ENUMERATE))
999 : {
1000 0 : return iterResultTemplate_; // = nullptr
1001 : }
1002 :
1003 0 : AutoSweepObjectGroup sweep(group);
1004 0 : if (!group->unknownProperties(sweep)) {
1005 : // Update `value` property typeset, since it can be any value.
1006 0 : HeapTypeSet* types = group->maybeGetProperty(sweep, NameToId(cx->names().value));
1007 0 : MOZ_ASSERT(types);
1008 : {
1009 0 : AutoEnterAnalysis enter(cx);
1010 0 : types->makeUnknown(sweep, cx);
1011 : }
1012 : }
1013 :
1014 : // Make sure that the properties are in the right slots.
1015 0 : DebugOnly<Shape*> shape = templateObject->lastProperty();
1016 0 : MOZ_ASSERT(shape->previous()->slot() == Realm::IterResultObjectValueSlot &&
1017 : shape->previous()->propidRef() == NameToId(cx->names().value));
1018 0 : MOZ_ASSERT(shape->slot() == Realm::IterResultObjectDoneSlot &&
1019 : shape->propidRef() == NameToId(cx->names().done));
1020 :
1021 0 : iterResultTemplate_.set(templateObject);
1022 :
1023 0 : return iterResultTemplate_;
1024 : }
1025 :
1026 : /*** Iterator objects ****************************************************************************/
1027 :
1028 : bool
1029 0 : js::IsPropertyIterator(HandleValue v)
1030 : {
1031 0 : return v.isObject() && v.toObject().is<PropertyIteratorObject>();
1032 : }
1033 :
1034 : size_t
1035 0 : PropertyIteratorObject::sizeOfMisc(mozilla::MallocSizeOf mallocSizeOf) const
1036 : {
1037 0 : return mallocSizeOf(getPrivate());
1038 : }
1039 :
1040 : void
1041 3 : PropertyIteratorObject::trace(JSTracer* trc, JSObject* obj)
1042 : {
1043 0 : if (NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator())
1044 0 : ni->trace(trc);
1045 3 : }
1046 :
1047 : void
1048 0 : PropertyIteratorObject::finalize(FreeOp* fop, JSObject* obj)
1049 : {
1050 0 : if (NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator())
1051 0 : fop->free_(ni);
1052 0 : }
1053 :
1054 : const ClassOps PropertyIteratorObject::classOps_ = {
1055 : nullptr, /* addProperty */
1056 : nullptr, /* delProperty */
1057 : nullptr, /* enumerate */
1058 : nullptr, /* newEnumerate */
1059 : nullptr, /* resolve */
1060 : nullptr, /* mayResolve */
1061 : finalize,
1062 : nullptr, /* call */
1063 : nullptr, /* hasInstance */
1064 : nullptr, /* construct */
1065 : trace
1066 : };
1067 :
1068 : const Class PropertyIteratorObject::class_ = {
1069 : "Iterator",
1070 : JSCLASS_HAS_PRIVATE |
1071 : JSCLASS_BACKGROUND_FINALIZE,
1072 : &PropertyIteratorObject::classOps_
1073 : };
1074 :
1075 : static const Class ArrayIteratorPrototypeClass = {
1076 : "Array Iterator",
1077 : 0
1078 : };
1079 :
1080 : enum {
1081 : ArrayIteratorSlotIteratedObject,
1082 : ArrayIteratorSlotNextIndex,
1083 : ArrayIteratorSlotItemKind,
1084 : ArrayIteratorSlotCount
1085 : };
1086 :
1087 : const Class ArrayIteratorObject::class_ = {
1088 : "Array Iterator",
1089 : JSCLASS_HAS_RESERVED_SLOTS(ArrayIteratorSlotCount)
1090 : };
1091 :
1092 :
1093 : ArrayIteratorObject*
1094 1596 : js::NewArrayIteratorObject(JSContext* cx, NewObjectKind newKind)
1095 : {
1096 0 : RootedObject proto(cx, GlobalObject::getOrCreateArrayIteratorPrototype(cx, cx->global()));
1097 1596 : if (!proto)
1098 : return nullptr;
1099 :
1100 3192 : return NewObjectWithGivenProto<ArrayIteratorObject>(cx, proto, newKind);
1101 : }
1102 :
1103 : static const JSFunctionSpec array_iterator_methods[] = {
1104 : JS_SELF_HOSTED_FN("next", "ArrayIteratorNext", 0, 0),
1105 : JS_FS_END
1106 : };
1107 :
1108 : static const Class StringIteratorPrototypeClass = {
1109 : "String Iterator",
1110 : 0
1111 : };
1112 :
1113 : enum {
1114 : StringIteratorSlotIteratedObject,
1115 : StringIteratorSlotNextIndex,
1116 : StringIteratorSlotCount
1117 : };
1118 :
1119 : const Class StringIteratorObject::class_ = {
1120 : "String Iterator",
1121 : JSCLASS_HAS_RESERVED_SLOTS(StringIteratorSlotCount)
1122 : };
1123 :
1124 : static const JSFunctionSpec string_iterator_methods[] = {
1125 : JS_SELF_HOSTED_FN("next", "StringIteratorNext", 0, 0),
1126 : JS_FS_END
1127 : };
1128 :
1129 : StringIteratorObject*
1130 0 : js::NewStringIteratorObject(JSContext* cx, NewObjectKind newKind)
1131 : {
1132 0 : RootedObject proto(cx, GlobalObject::getOrCreateStringIteratorPrototype(cx, cx->global()));
1133 0 : if (!proto)
1134 : return nullptr;
1135 :
1136 0 : return NewObjectWithGivenProto<StringIteratorObject>(cx, proto, newKind);
1137 : }
1138 :
1139 : JSObject*
1140 147 : js::ValueToIterator(JSContext* cx, HandleValue vp)
1141 : {
1142 0 : RootedObject obj(cx);
1143 147 : if (vp.isObject()) {
1144 : /* Common case. */
1145 0 : obj = &vp.toObject();
1146 5 : } else if (vp.isNullOrUndefined()) {
1147 : /*
1148 : * Enumerating over null and undefined gives an empty enumerator, so
1149 : * that |for (var p in <null or undefined>) <loop>;| never executes
1150 : * <loop>, per ES5 12.6.4.
1151 : */
1152 5 : return NewEmptyPropertyIterator(cx);
1153 : } else {
1154 0 : obj = ToObject(cx, vp);
1155 0 : if (!obj)
1156 : return nullptr;
1157 : }
1158 :
1159 142 : return GetIterator(cx, obj);
1160 : }
1161 :
1162 : void
1163 273 : js::CloseIterator(JSObject* obj)
1164 : {
1165 273 : if (obj->is<PropertyIteratorObject>()) {
1166 : /* Remove enumerators from the active list, which is a stack. */
1167 546 : NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator();
1168 :
1169 273 : ni->unlink();
1170 :
1171 0 : MOZ_ASSERT(ni->isActive());
1172 273 : ni->markInactive();
1173 :
1174 : // Reset the enumerator; it may still be in the cached iterators for
1175 : // this thread and can be reused.
1176 273 : ni->resetPropertyCursorForReuse();
1177 : }
1178 273 : }
1179 :
1180 : bool
1181 0 : js::IteratorCloseForException(JSContext* cx, HandleObject obj)
1182 : {
1183 0 : MOZ_ASSERT(cx->isExceptionPending());
1184 :
1185 0 : bool isClosingGenerator = cx->isClosingGenerator();
1186 0 : JS::AutoSaveExceptionState savedExc(cx);
1187 :
1188 : // Implements IteratorClose (ES 7.4.6) for exception unwinding. See
1189 : // also the bytecode generated by BytecodeEmitter::emitIteratorClose.
1190 :
1191 : // Step 3.
1192 : //
1193 : // Get the "return" method.
1194 0 : RootedValue returnMethod(cx);
1195 0 : if (!GetProperty(cx, obj, obj, cx->names().return_, &returnMethod))
1196 : return false;
1197 :
1198 : // Step 4.
1199 : //
1200 : // Do nothing if "return" is null or undefined. Throw a TypeError if the
1201 : // method is not IsCallable.
1202 0 : if (returnMethod.isNullOrUndefined())
1203 : return true;
1204 0 : if (!IsCallable(returnMethod))
1205 0 : return ReportIsNotFunction(cx, returnMethod);
1206 :
1207 : // Step 5, 6, 8.
1208 : //
1209 : // Call "return" if it is not null or undefined.
1210 0 : RootedValue rval(cx);
1211 0 : bool ok = Call(cx, returnMethod, obj, &rval);
1212 0 : if (isClosingGenerator) {
1213 : // Closing an iterator is implemented as an exception, but in spec
1214 : // terms it is a Completion value with [[Type]] return. In this case
1215 : // we *do* care if the call threw and if it returned an object.
1216 0 : if (!ok)
1217 : return false;
1218 0 : if (!rval.isObject())
1219 0 : return ThrowCheckIsObject(cx, CheckIsObjectKind::IteratorReturn);
1220 : } else {
1221 : // We don't care if the call threw or that it returned an Object, as
1222 : // Step 6 says if IteratorClose is being called during a throw, the
1223 : // original throw has primacy.
1224 0 : savedExc.restore();
1225 : }
1226 :
1227 : return true;
1228 : }
1229 :
1230 : void
1231 0 : js::UnwindIteratorForUncatchableException(JSObject* obj)
1232 : {
1233 0 : if (obj->is<PropertyIteratorObject>()) {
1234 0 : NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator();
1235 0 : ni->unlink();
1236 : }
1237 0 : }
1238 :
1239 : static bool
1240 1 : SuppressDeletedProperty(JSContext* cx, NativeIterator* ni, HandleObject obj,
1241 : Handle<JSFlatString*> str)
1242 : {
1243 2 : if (ni->objectBeingIterated() != obj)
1244 : return true;
1245 :
1246 : // Optimization for the following common case:
1247 : //
1248 : // for (var p in o) {
1249 : // delete o[p];
1250 : // }
1251 : //
1252 : // Note that usually both strings will be atoms so we only check for pointer
1253 : // equality here.
1254 1 : if (ni->previousPropertyWas(str))
1255 : return true;
1256 :
1257 : while (true) {
1258 0 : bool restart = false;
1259 :
1260 : // Check whether id is still to come.
1261 0 : GCPtrFlatString* const cursor = ni->nextProperty();
1262 0 : GCPtrFlatString* const end = ni->propertiesEnd();
1263 0 : for (GCPtrFlatString* idp = cursor; idp < end; ++idp) {
1264 : // Common case: both strings are atoms.
1265 0 : if ((*idp)->isAtom() && str->isAtom()) {
1266 0 : if (*idp != str)
1267 0 : continue;
1268 : } else {
1269 0 : if (!EqualStrings(*idp, str))
1270 : continue;
1271 : }
1272 :
1273 : // Check whether another property along the prototype chain became
1274 : // visible as a result of this deletion.
1275 0 : RootedObject proto(cx);
1276 0 : if (!GetPrototype(cx, obj, &proto))
1277 0 : return false;
1278 0 : if (proto) {
1279 0 : RootedId id(cx);
1280 0 : RootedValue idv(cx, StringValue(*idp));
1281 0 : if (!ValueToId<CanGC>(cx, idv, &id))
1282 0 : return false;
1283 :
1284 0 : Rooted<PropertyDescriptor> desc(cx);
1285 0 : if (!GetPropertyDescriptor(cx, proto, id, &desc))
1286 0 : return false;
1287 :
1288 0 : if (desc.object() && desc.enumerable())
1289 0 : continue;
1290 : }
1291 :
1292 : // If GetPropertyDescriptor above removed a property from ni, start
1293 : // over.
1294 0 : if (end != ni->propertiesEnd() || cursor != ni->nextProperty()) {
1295 0 : restart = true;
1296 0 : break;
1297 : }
1298 :
1299 : // No property along the prototype chain stepped in to take the
1300 : // property's place, so go ahead and delete id from the list.
1301 : // If it is the next property to be enumerated, just skip it.
1302 0 : if (idp == cursor) {
1303 0 : ni->incCursor();
1304 : } else {
1305 0 : for (GCPtrFlatString* p = idp; p + 1 != end; p++)
1306 0 : *p = *(p + 1);
1307 :
1308 0 : ni->trimLastProperty();
1309 : }
1310 :
1311 0 : ni->markHasUnvisitedPropertyDeletion();
1312 0 : return true;
1313 : }
1314 :
1315 0 : if (!restart)
1316 : return true;
1317 : }
1318 : }
1319 :
1320 : /*
1321 : * Suppress enumeration of deleted properties. This function must be called
1322 : * when a property is deleted and there might be active enumerators.
1323 : *
1324 : * We maintain a list of active non-escaping for-in enumerators. To suppress
1325 : * a property, we check whether each active enumerator contains the (obj, id)
1326 : * pair and has not yet enumerated |id|. If so, and |id| is the next property,
1327 : * we simply advance the cursor. Otherwise, we delete |id| from the list.
1328 : *
1329 : * We do not suppress enumeration of a property deleted along an object's
1330 : * prototype chain. Only direct deletions on the object are handled.
1331 : */
1332 : static bool
1333 1 : SuppressDeletedPropertyHelper(JSContext* cx, HandleObject obj, Handle<JSFlatString*> str)
1334 : {
1335 0 : NativeIterator* enumeratorList = ObjectRealm::get(obj).enumerators;
1336 1 : NativeIterator* ni = enumeratorList->next();
1337 :
1338 0 : while (ni != enumeratorList) {
1339 1 : if (!SuppressDeletedProperty(cx, ni, obj, str))
1340 : return false;
1341 1 : ni = ni->next();
1342 : }
1343 :
1344 : return true;
1345 : }
1346 :
1347 : bool
1348 133 : js::SuppressDeletedProperty(JSContext* cx, HandleObject obj, jsid id)
1349 : {
1350 399 : if (MOZ_LIKELY(!ObjectRealm::get(obj).objectMaybeInIteration(obj)))
1351 : return true;
1352 :
1353 1 : if (JSID_IS_SYMBOL(id))
1354 : return true;
1355 :
1356 0 : Rooted<JSFlatString*> str(cx, IdToString(cx, id));
1357 1 : if (!str)
1358 : return false;
1359 1 : return SuppressDeletedPropertyHelper(cx, obj, str);
1360 : }
1361 :
1362 : bool
1363 104 : js::SuppressDeletedElement(JSContext* cx, HandleObject obj, uint32_t index)
1364 : {
1365 312 : if (MOZ_LIKELY(!ObjectRealm::get(obj).objectMaybeInIteration(obj)))
1366 : return true;
1367 :
1368 0 : RootedId id(cx);
1369 0 : if (!IndexToId(cx, index, &id))
1370 : return false;
1371 :
1372 0 : Rooted<JSFlatString*> str(cx, IdToString(cx, id));
1373 0 : if (!str)
1374 : return false;
1375 0 : return SuppressDeletedPropertyHelper(cx, obj, str);
1376 : }
1377 :
1378 : bool
1379 195 : js::IteratorMore(JSContext* cx, HandleObject iterobj, MutableHandleValue rval)
1380 : {
1381 : // Fast path for native iterators.
1382 0 : if (MOZ_LIKELY(iterobj->is<PropertyIteratorObject>())) {
1383 0 : NativeIterator* ni = iterobj->as<PropertyIteratorObject>().getNativeIterator();
1384 0 : rval.set(ni->nextIteratedValueAndAdvance());
1385 195 : return true;
1386 : }
1387 :
1388 0 : if (JS_IsDeadWrapper(iterobj)) {
1389 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
1390 0 : return false;
1391 : }
1392 :
1393 0 : MOZ_ASSERT(IsWrapper(iterobj));
1394 :
1395 0 : RootedObject obj(cx, CheckedUnwrap(iterobj));
1396 0 : if (!obj)
1397 : return false;
1398 :
1399 0 : MOZ_RELEASE_ASSERT(obj->is<PropertyIteratorObject>());
1400 : {
1401 0 : AutoRealm ar(cx, obj);
1402 0 : NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator();
1403 0 : rval.set(ni->nextIteratedValueAndAdvance());
1404 : }
1405 0 : return cx->compartment()->wrap(cx, rval);
1406 : }
1407 :
1408 : static const JSFunctionSpec iterator_proto_methods[] = {
1409 : JS_SELF_HOSTED_SYM_FN(iterator, "IteratorIdentity", 0, 0),
1410 : JS_FS_END
1411 : };
1412 :
1413 : /* static */ bool
1414 18 : GlobalObject::initIteratorProto(JSContext* cx, Handle<GlobalObject*> global)
1415 : {
1416 18 : if (global->getReservedSlot(ITERATOR_PROTO).isObject())
1417 : return true;
1418 :
1419 0 : RootedObject proto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
1420 54 : if (!proto || !DefinePropertiesAndFunctions(cx, proto, nullptr, iterator_proto_methods))
1421 : return false;
1422 :
1423 0 : global->setReservedSlot(ITERATOR_PROTO, ObjectValue(*proto));
1424 18 : return true;
1425 : }
1426 :
1427 : /* static */ bool
1428 16 : GlobalObject::initArrayIteratorProto(JSContext* cx, Handle<GlobalObject*> global)
1429 : {
1430 16 : if (global->getReservedSlot(ARRAY_ITERATOR_PROTO).isObject())
1431 : return true;
1432 :
1433 0 : RootedObject iteratorProto(cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
1434 16 : if (!iteratorProto)
1435 : return false;
1436 :
1437 0 : const Class* cls = &ArrayIteratorPrototypeClass;
1438 0 : RootedObject proto(cx, GlobalObject::createBlankPrototypeInheriting(cx, cls, iteratorProto));
1439 0 : if (!proto ||
1440 0 : !DefinePropertiesAndFunctions(cx, proto, nullptr, array_iterator_methods) ||
1441 64 : !DefineToStringTag(cx, proto, cx->names().ArrayIterator))
1442 : {
1443 : return false;
1444 : }
1445 :
1446 0 : global->setReservedSlot(ARRAY_ITERATOR_PROTO, ObjectValue(*proto));
1447 16 : return true;
1448 : }
1449 :
1450 : /* static */ bool
1451 0 : GlobalObject::initStringIteratorProto(JSContext* cx, Handle<GlobalObject*> global)
1452 : {
1453 0 : if (global->getReservedSlot(STRING_ITERATOR_PROTO).isObject())
1454 : return true;
1455 :
1456 0 : RootedObject iteratorProto(cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
1457 0 : if (!iteratorProto)
1458 : return false;
1459 :
1460 0 : const Class* cls = &StringIteratorPrototypeClass;
1461 0 : RootedObject proto(cx, GlobalObject::createBlankPrototypeInheriting(cx, cls, iteratorProto));
1462 0 : if (!proto ||
1463 0 : !DefinePropertiesAndFunctions(cx, proto, nullptr, string_iterator_methods) ||
1464 0 : !DefineToStringTag(cx, proto, cx->names().StringIterator))
1465 : {
1466 : return false;
1467 : }
1468 :
1469 0 : global->setReservedSlot(STRING_ITERATOR_PROTO, ObjectValue(*proto));
1470 : return true;
1471 : }
|