1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
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/ArrayUtils.h"
12 #include "mozilla/DebugOnly.h"
13 #include "mozilla/Likely.h"
14 #include "mozilla/Maybe.h"
15 #include "mozilla/MemoryReporting.h"
16 #include "mozilla/PodOperations.h"
17
18 #include <algorithm>
19 #include <new>
20
21 #include "jstypes.h"
22
23 #include "builtin/Array.h"
24 #include "builtin/SelfHostingDefines.h"
25 #include "ds/Sort.h"
26 #include "gc/FreeOp.h"
27 #include "gc/Marking.h"
28 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
29 #include "js/PropertySpec.h"
30 #include "js/Proxy.h"
31 #include "util/DifferentialTesting.h"
32 #include "util/Poison.h"
33 #include "vm/BytecodeUtil.h"
34 #include "vm/GlobalObject.h"
35 #include "vm/Interpreter.h"
36 #include "vm/JSAtom.h"
37 #include "vm/JSContext.h"
38 #include "vm/JSObject.h"
39 #include "vm/JSScript.h"
40 #include "vm/NativeObject.h" // js::PlainObject
41 #include "vm/Shape.h"
42 #include "vm/TypedArrayObject.h"
43 #include "vm/WellKnownAtom.h" // js_*_str
44
45 #include "vm/Compartment-inl.h"
46 #include "vm/JSScript-inl.h"
47 #include "vm/NativeObject-inl.h"
48 #include "vm/PlainObject-inl.h" // js::PlainObject::createWithTemplate
49 #include "vm/Stack-inl.h"
50 #include "vm/StringType-inl.h"
51
52 using namespace js;
53
54 using mozilla::ArrayEqual;
55 using mozilla::DebugOnly;
56 using mozilla::Maybe;
57 using mozilla::PodCopy;
58
59 using RootedPropertyIteratorObject = Rooted<PropertyIteratorObject*>;
60
61 static const gc::AllocKind ITERATOR_FINALIZE_KIND =
62 gc::AllocKind::OBJECT2_BACKGROUND;
63
64 // Beware! This function may have to trace incompletely-initialized
65 // |NativeIterator| allocations if the |IdToString| in that constructor recurs
66 // into this code.
trace(JSTracer * trc)67 void NativeIterator::trace(JSTracer* trc) {
68 TraceNullableEdge(trc, &objectBeingIterated_, "objectBeingIterated_");
69 TraceNullableEdge(trc, &iterObj_, "iterObj");
70
71 // The limits below are correct at every instant of |NativeIterator|
72 // initialization, with the end-pointer incremented as each new shape is
73 // created, so they're safe to use here.
74 std::for_each(shapesBegin(), shapesEnd(), [trc](GCPtrShape& shape) {
75 TraceEdge(trc, &shape, "iterator_shape");
76 });
77
78 // But as properties must be created *before* shapes, |propertiesBegin()|
79 // that depends on |shapesEnd()| having its final value can't safely be
80 // used. Until this is fully initialized, use |propertyCursor_| instead,
81 // which points at the start of properties even in partially initialized
82 // |NativeIterator|s. (|propertiesEnd()| is safe at all times with respect
83 // to the properly-chosen beginning.)
84 //
85 // Note that we must trace all properties (not just those not yet visited,
86 // or just visited, due to |NativeIterator::previousPropertyWas|) for
87 // |NativeIterator|s to be reusable.
88 GCPtrLinearString* begin =
89 MOZ_LIKELY(isInitialized()) ? propertiesBegin() : propertyCursor_;
90 std::for_each(begin, propertiesEnd(), [trc](GCPtrLinearString& prop) {
91 // Properties begin life non-null and never *become*
92 // null. (Deletion-suppression will shift trailing
93 // properties over a deleted property in the properties
94 // array, but it doesn't null them out.)
95 TraceEdge(trc, &prop, "prop");
96 });
97 }
98
99 using PropertyKeySet = GCHashSet<PropertyKey, DefaultHasher<PropertyKey>>;
100
101 template <bool CheckForDuplicates>
Enumerate(JSContext * cx,HandleObject pobj,jsid id,bool enumerable,unsigned flags,MutableHandle<PropertyKeySet> visited,MutableHandleIdVector props)102 static inline bool Enumerate(JSContext* cx, HandleObject pobj, jsid id,
103 bool enumerable, unsigned flags,
104 MutableHandle<PropertyKeySet> visited,
105 MutableHandleIdVector props) {
106 if (CheckForDuplicates) {
107 // If we've already seen this, we definitely won't add it.
108 PropertyKeySet::AddPtr p = visited.lookupForAdd(id);
109 if (MOZ_UNLIKELY(!!p)) {
110 return true;
111 }
112
113 // It's not necessary to add properties to the hash set at the end of
114 // the prototype chain, but custom enumeration behaviors might return
115 // duplicated properties, so always add in such cases.
116 if (pobj->is<ProxyObject>() || pobj->staticPrototype() ||
117 pobj->getClass()->getNewEnumerate()) {
118 if (!visited.add(p, id)) {
119 return false;
120 }
121 }
122 }
123
124 if (!enumerable && !(flags & JSITER_HIDDEN)) {
125 return true;
126 }
127
128 // Symbol-keyed properties and nonenumerable properties are skipped unless
129 // the caller specifically asks for them. A caller can also filter out
130 // non-symbols by asking for JSITER_SYMBOLSONLY. PrivateName symbols are
131 // skipped unless JSITER_PRIVATE is passed.
132 if (id.isSymbol()) {
133 if (!(flags & JSITER_SYMBOLS)) {
134 return true;
135 }
136 if (!(flags & JSITER_PRIVATE) && id.isPrivateName()) {
137 return true;
138 }
139 } else {
140 if ((flags & JSITER_SYMBOLSONLY)) {
141 return true;
142 }
143 }
144
145 return props.append(id);
146 }
147
EnumerateExtraProperties(JSContext * cx,HandleObject obj,unsigned flags,MutableHandle<PropertyKeySet> visited,MutableHandleIdVector props)148 static bool EnumerateExtraProperties(JSContext* cx, HandleObject obj,
149 unsigned flags,
150 MutableHandle<PropertyKeySet> visited,
151 MutableHandleIdVector props) {
152 MOZ_ASSERT(obj->getClass()->getNewEnumerate());
153
154 RootedIdVector properties(cx);
155 bool enumerableOnly = !(flags & JSITER_HIDDEN);
156 if (!obj->getClass()->getNewEnumerate()(cx, obj, &properties,
157 enumerableOnly)) {
158 return false;
159 }
160
161 RootedId id(cx);
162 for (size_t n = 0; n < properties.length(); n++) {
163 id = properties[n];
164
165 // The enumerate hook does not indicate whether the properties
166 // it returns are enumerable or not. Since we already passed
167 // `enumerableOnly` to the hook to filter out non-enumerable
168 // properties, it doesn't really matter what we pass here.
169 bool enumerable = true;
170 if (!Enumerate<true>(cx, obj, id, enumerable, flags, visited, props)) {
171 return false;
172 }
173 }
174
175 return true;
176 }
177
SortComparatorIntegerIds(jsid a,jsid b,bool * lessOrEqualp)178 static bool SortComparatorIntegerIds(jsid a, jsid b, bool* lessOrEqualp) {
179 uint32_t indexA, indexB;
180 MOZ_ALWAYS_TRUE(IdIsIndex(a, &indexA));
181 MOZ_ALWAYS_TRUE(IdIsIndex(b, &indexB));
182 *lessOrEqualp = (indexA <= indexB);
183 return true;
184 }
185
186 template <bool CheckForDuplicates>
EnumerateNativeProperties(JSContext * cx,HandleNativeObject pobj,unsigned flags,MutableHandle<PropertyKeySet> visited,MutableHandleIdVector props)187 static bool EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj,
188 unsigned flags,
189 MutableHandle<PropertyKeySet> visited,
190 MutableHandleIdVector props) {
191 bool enumerateSymbols;
192 if (flags & JSITER_SYMBOLSONLY) {
193 enumerateSymbols = true;
194 } else {
195 // Collect any dense elements from this object.
196 size_t firstElemIndex = props.length();
197 size_t initlen = pobj->getDenseInitializedLength();
198 const Value* vp = pobj->getDenseElements();
199 bool hasHoles = false;
200 for (size_t i = 0; i < initlen; ++i, ++vp) {
201 if (vp->isMagic(JS_ELEMENTS_HOLE)) {
202 hasHoles = true;
203 } else {
204 // Dense arrays never get so large that i would not fit into an
205 // integer id.
206 if (!Enumerate<CheckForDuplicates>(cx, pobj, INT_TO_JSID(i),
207 /* enumerable = */ true, flags,
208 visited, props)) {
209 return false;
210 }
211 }
212 }
213
214 // Collect any typed array or shared typed array elements from this
215 // object.
216 if (pobj->is<TypedArrayObject>()) {
217 size_t len = pobj->as<TypedArrayObject>().length();
218
219 // Fail early if the typed array is enormous, because this will be very
220 // slow and will likely report OOM. This also means we don't need to
221 // handle indices greater than JSID_INT_MAX in the loop below.
222 static_assert(JSID_INT_MAX == INT32_MAX);
223 if (len > INT32_MAX) {
224 ReportOutOfMemory(cx);
225 return false;
226 }
227
228 for (size_t i = 0; i < len; i++) {
229 if (!Enumerate<CheckForDuplicates>(cx, pobj, INT_TO_JSID(i),
230 /* enumerable = */ true, flags,
231 visited, props)) {
232 return false;
233 }
234 }
235 }
236
237 // Collect any sparse elements from this object.
238 bool isIndexed = pobj->isIndexed();
239 if (isIndexed) {
240 // If the dense elements didn't have holes, we don't need to include
241 // them in the sort.
242 if (!hasHoles) {
243 firstElemIndex = props.length();
244 }
245
246 for (ShapePropertyIter<NoGC> iter(pobj->shape()); !iter.done(); iter++) {
247 jsid id = iter->key();
248 uint32_t dummy;
249 if (IdIsIndex(id, &dummy)) {
250 if (!Enumerate<CheckForDuplicates>(cx, pobj, id, iter->enumerable(),
251 flags, visited, props)) {
252 return false;
253 }
254 }
255 }
256
257 MOZ_ASSERT(firstElemIndex <= props.length());
258
259 jsid* ids = props.begin() + firstElemIndex;
260 size_t n = props.length() - firstElemIndex;
261
262 RootedIdVector tmp(cx);
263 if (!tmp.resize(n)) {
264 return false;
265 }
266 PodCopy(tmp.begin(), ids, n);
267
268 if (!MergeSort(ids, n, tmp.begin(), SortComparatorIntegerIds)) {
269 return false;
270 }
271 }
272
273 size_t initialLength = props.length();
274
275 /* Collect all unique property names from this object's shape. */
276 bool symbolsFound = false;
277 for (ShapePropertyIter<NoGC> iter(pobj->shape()); !iter.done(); iter++) {
278 jsid id = iter->key();
279
280 if (id.isSymbol()) {
281 symbolsFound = true;
282 continue;
283 }
284
285 uint32_t dummy;
286 if (isIndexed && IdIsIndex(id, &dummy)) {
287 continue;
288 }
289
290 if (!Enumerate<CheckForDuplicates>(cx, pobj, id, iter->enumerable(),
291 flags, visited, props)) {
292 return false;
293 }
294 }
295 std::reverse(props.begin() + initialLength, props.end());
296
297 enumerateSymbols = symbolsFound && (flags & JSITER_SYMBOLS);
298 }
299
300 if (enumerateSymbols) {
301 // Do a second pass to collect symbols. ES6 draft rev 25 (2014 May 22)
302 // 9.1.12 requires that all symbols appear after all strings in the
303 // result.
304 size_t initialLength = props.length();
305 for (ShapePropertyIter<NoGC> iter(pobj->shape()); !iter.done(); iter++) {
306 jsid id = iter->key();
307 if (id.isSymbol()) {
308 if (!Enumerate<CheckForDuplicates>(cx, pobj, id, iter->enumerable(),
309 flags, visited, props)) {
310 return false;
311 }
312 }
313 }
314 std::reverse(props.begin() + initialLength, props.end());
315 }
316
317 return true;
318 }
319
EnumerateNativeProperties(JSContext * cx,HandleNativeObject pobj,unsigned flags,MutableHandle<PropertyKeySet> visited,MutableHandleIdVector props,bool checkForDuplicates)320 static bool EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj,
321 unsigned flags,
322 MutableHandle<PropertyKeySet> visited,
323 MutableHandleIdVector props,
324 bool checkForDuplicates) {
325 if (checkForDuplicates) {
326 return EnumerateNativeProperties<true>(cx, pobj, flags, visited, props);
327 }
328 return EnumerateNativeProperties<false>(cx, pobj, flags, visited, props);
329 }
330
331 template <bool CheckForDuplicates>
EnumerateProxyProperties(JSContext * cx,HandleObject pobj,unsigned flags,MutableHandle<PropertyKeySet> visited,MutableHandleIdVector props)332 static bool EnumerateProxyProperties(JSContext* cx, HandleObject pobj,
333 unsigned flags,
334 MutableHandle<PropertyKeySet> visited,
335 MutableHandleIdVector props) {
336 MOZ_ASSERT(pobj->is<ProxyObject>());
337
338 RootedIdVector proxyProps(cx);
339
340 if (flags & JSITER_HIDDEN || flags & JSITER_SYMBOLS) {
341 // This gets all property keys, both strings and symbols. The call to
342 // Enumerate in the loop below will filter out unwanted keys, per the
343 // flags.
344 if (!Proxy::ownPropertyKeys(cx, pobj, &proxyProps)) {
345 return false;
346 }
347
348 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
349 for (size_t n = 0, len = proxyProps.length(); n < len; n++) {
350 bool enumerable = false;
351
352 // We need to filter, if the caller just wants enumerable symbols.
353 if (!(flags & JSITER_HIDDEN)) {
354 if (!Proxy::getOwnPropertyDescriptor(cx, pobj, proxyProps[n], &desc)) {
355 return false;
356 }
357 enumerable = desc.isSome() && desc->enumerable();
358 }
359
360 if (!Enumerate<CheckForDuplicates>(cx, pobj, proxyProps[n], enumerable,
361 flags, visited, props)) {
362 return false;
363 }
364 }
365
366 return true;
367 }
368
369 // Returns enumerable property names (no symbols).
370 if (!Proxy::getOwnEnumerablePropertyKeys(cx, pobj, &proxyProps)) {
371 return false;
372 }
373
374 for (size_t n = 0, len = proxyProps.length(); n < len; n++) {
375 if (!Enumerate<CheckForDuplicates>(cx, pobj, proxyProps[n], true, flags,
376 visited, props)) {
377 return false;
378 }
379 }
380
381 return true;
382 }
383
384 #ifdef DEBUG
385
386 struct SortComparatorIds {
387 JSContext* const cx;
388
SortComparatorIdsSortComparatorIds389 explicit SortComparatorIds(JSContext* cx) : cx(cx) {}
390
operator ()SortComparatorIds391 bool operator()(jsid aArg, jsid bArg, bool* lessOrEqualp) {
392 RootedId a(cx, aArg);
393 RootedId b(cx, bArg);
394
395 // Pick an arbitrary order on jsids that is as stable as possible
396 // across executions.
397 if (a == b) {
398 *lessOrEqualp = true;
399 return true;
400 }
401
402 size_t ta = JSID_BITS(a.get()) & JSID_TYPE_MASK;
403 size_t tb = JSID_BITS(b.get()) & JSID_TYPE_MASK;
404 if (ta != tb) {
405 *lessOrEqualp = (ta <= tb);
406 return true;
407 }
408
409 if (JSID_IS_INT(a)) {
410 *lessOrEqualp = (JSID_TO_INT(a) <= JSID_TO_INT(b));
411 return true;
412 }
413
414 RootedString astr(cx), bstr(cx);
415 if (a.isSymbol()) {
416 MOZ_ASSERT(b.isSymbol());
417 JS::SymbolCode ca = a.toSymbol()->code();
418 JS::SymbolCode cb = b.toSymbol()->code();
419 if (ca != cb) {
420 *lessOrEqualp = uint32_t(ca) <= uint32_t(cb);
421 return true;
422 }
423 MOZ_ASSERT(ca == JS::SymbolCode::PrivateNameSymbol ||
424 ca == JS::SymbolCode::InSymbolRegistry ||
425 ca == JS::SymbolCode::UniqueSymbol);
426 astr = a.toSymbol()->description();
427 bstr = b.toSymbol()->description();
428 if (!astr || !bstr) {
429 *lessOrEqualp = !astr;
430 return true;
431 }
432
433 // Fall through to string comparison on the descriptions. The sort
434 // order is nondeterministic if two different unique symbols have
435 // the same description.
436 } else {
437 astr = IdToString(cx, a);
438 if (!astr) {
439 return false;
440 }
441 bstr = IdToString(cx, b);
442 if (!bstr) {
443 return false;
444 }
445 }
446
447 int32_t result;
448 if (!CompareStrings(cx, astr, bstr, &result)) {
449 return false;
450 }
451
452 *lessOrEqualp = (result <= 0);
453 return true;
454 }
455 };
456
457 #endif /* DEBUG */
458
Snapshot(JSContext * cx,HandleObject pobj_,unsigned flags,MutableHandleIdVector props)459 static bool Snapshot(JSContext* cx, HandleObject pobj_, unsigned flags,
460 MutableHandleIdVector props) {
461 Rooted<PropertyKeySet> visited(cx, PropertyKeySet(cx));
462 RootedObject pobj(cx, pobj_);
463
464 // Don't check for duplicates if we're only interested in own properties.
465 // This does the right thing for most objects: native objects don't have
466 // duplicate property ids and we allow the [[OwnPropertyKeys]] proxy trap to
467 // return duplicates.
468 //
469 // The only special case is when the object has a newEnumerate hook: it
470 // can return duplicate properties and we have to filter them. This is
471 // handled below.
472 bool checkForDuplicates = !(flags & JSITER_OWNONLY);
473
474 do {
475 if (pobj->getClass()->getNewEnumerate()) {
476 if (!EnumerateExtraProperties(cx, pobj, flags, &visited, props)) {
477 return false;
478 }
479
480 if (pobj->is<NativeObject>()) {
481 if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags,
482 &visited, props, true)) {
483 return false;
484 }
485 }
486
487 } else if (pobj->is<NativeObject>()) {
488 // Give the object a chance to resolve all lazy properties
489 if (JSEnumerateOp enumerate = pobj->getClass()->getEnumerate()) {
490 if (!enumerate(cx, pobj.as<NativeObject>())) {
491 return false;
492 }
493 }
494 if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags,
495 &visited, props, checkForDuplicates)) {
496 return false;
497 }
498 } else if (pobj->is<ProxyObject>()) {
499 if (checkForDuplicates) {
500 if (!EnumerateProxyProperties<true>(cx, pobj, flags, &visited, props)) {
501 return false;
502 }
503 } else {
504 if (!EnumerateProxyProperties<false>(cx, pobj, flags, &visited,
505 props)) {
506 return false;
507 }
508 }
509 } else {
510 MOZ_CRASH("non-native objects must have an enumerate op");
511 }
512
513 if (flags & JSITER_OWNONLY) {
514 break;
515 }
516
517 if (!GetPrototype(cx, pobj, &pobj)) {
518 return false;
519 }
520
521 // The [[Prototype]] chain might be cyclic.
522 if (!CheckForInterrupt(cx)) {
523 return false;
524 }
525 } while (pobj != nullptr);
526
527 #ifdef DEBUG
528 if (js::SupportDifferentialTesting()) {
529 /*
530 * In some cases the enumeration order for an object depends on the
531 * execution mode (interpreter vs. JIT), especially for native objects
532 * with a class enumerate hook (where resolving a property changes the
533 * resulting enumeration order). These aren't really bugs, but the
534 * differences can change the generated output and confuse correctness
535 * fuzzers, so we sort the ids if such a fuzzer is running.
536 *
537 * We don't do this in the general case because (a) doing so is slow,
538 * and (b) it also breaks the web, which expects enumeration order to
539 * follow the order in which properties are added, in certain cases.
540 * Since ECMA does not specify an enumeration order for objects, both
541 * behaviors are technically correct to do.
542 */
543
544 jsid* ids = props.begin();
545 size_t n = props.length();
546
547 RootedIdVector tmp(cx);
548 if (!tmp.resize(n)) {
549 return false;
550 }
551 PodCopy(tmp.begin(), ids, n);
552
553 if (!MergeSort(ids, n, tmp.begin(), SortComparatorIds(cx))) {
554 return false;
555 }
556 }
557 #endif
558
559 return true;
560 }
561
GetPropertyKeys(JSContext * cx,HandleObject obj,unsigned flags,MutableHandleIdVector props)562 JS_PUBLIC_API bool js::GetPropertyKeys(JSContext* cx, HandleObject obj,
563 unsigned flags,
564 MutableHandleIdVector props) {
565 return Snapshot(cx, obj,
566 flags & (JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS |
567 JSITER_SYMBOLSONLY | JSITER_PRIVATE),
568 props);
569 }
570
RegisterEnumerator(ObjectRealm & realm,NativeIterator * ni)571 static inline void RegisterEnumerator(ObjectRealm& realm, NativeIterator* ni) {
572 // Register non-escaping native enumerators (for-in) with the current
573 // context.
574 ni->link(realm.enumerators);
575
576 MOZ_ASSERT(!ni->isActive());
577 ni->markActive();
578 }
579
NewPropertyIteratorObject(JSContext * cx)580 static PropertyIteratorObject* NewPropertyIteratorObject(JSContext* cx) {
581 const JSClass* clasp = &PropertyIteratorObject::class_;
582 RootedShape shape(cx, SharedShape::getInitialShape(cx, clasp, cx->realm(),
583 TaggedProto(nullptr),
584 ITERATOR_FINALIZE_KIND));
585 if (!shape) {
586 return nullptr;
587 }
588
589 JSObject* obj;
590 JS_TRY_VAR_OR_RETURN_NULL(
591 cx, obj,
592 NativeObject::create(cx, ITERATOR_FINALIZE_KIND,
593 GetInitialHeap(GenericObject, clasp), shape));
594
595 PropertyIteratorObject* res = &obj->as<PropertyIteratorObject>();
596
597 // CodeGenerator::visitIteratorStartO assumes the iterator object is not
598 // inside the nursery when deciding whether a barrier is necessary.
599 MOZ_ASSERT(!js::gc::IsInsideNursery(res));
600
601 MOZ_ASSERT(res->numFixedSlots() == PropertyIteratorObject::NUM_FIXED_SLOTS);
602 return res;
603 }
604
NumTrailingWords(size_t propertyCount,size_t shapeCount)605 static inline size_t NumTrailingWords(size_t propertyCount, size_t shapeCount) {
606 static_assert(sizeof(GCPtrLinearString) == sizeof(uintptr_t));
607 static_assert(sizeof(GCPtrShape) == sizeof(uintptr_t));
608 return propertyCount + shapeCount;
609 }
610
AllocationSize(size_t propertyCount,size_t shapeCount)611 static inline size_t AllocationSize(size_t propertyCount, size_t shapeCount) {
612 return sizeof(NativeIterator) +
613 (NumTrailingWords(propertyCount, shapeCount) * sizeof(uintptr_t));
614 }
615
CreatePropertyIterator(JSContext * cx,Handle<JSObject * > objBeingIterated,HandleIdVector props,uint32_t numShapes,HashNumber shapesHash)616 static PropertyIteratorObject* CreatePropertyIterator(
617 JSContext* cx, Handle<JSObject*> objBeingIterated, HandleIdVector props,
618 uint32_t numShapes, HashNumber shapesHash) {
619 if (props.length() > NativeIterator::PropCountLimit) {
620 ReportAllocationOverflow(cx);
621 return nullptr;
622 }
623
624 Rooted<PropertyIteratorObject*> propIter(cx, NewPropertyIteratorObject(cx));
625 if (!propIter) {
626 return nullptr;
627 }
628
629 void* mem = cx->pod_malloc_with_extra<NativeIterator, uintptr_t>(
630 NumTrailingWords(props.length(), numShapes));
631 if (!mem) {
632 return nullptr;
633 }
634
635 // This also registers |ni| with |propIter|.
636 bool hadError = false;
637 NativeIterator* ni = new (mem) NativeIterator(
638 cx, propIter, objBeingIterated, props, numShapes, shapesHash, &hadError);
639 if (hadError) {
640 return nullptr;
641 }
642
643 ObjectRealm& realm = objBeingIterated ? ObjectRealm::get(objBeingIterated)
644 : ObjectRealm::get(propIter);
645 RegisterEnumerator(realm, ni);
646
647 return propIter;
648 }
649
650 /**
651 * Initialize a sentinel NativeIterator whose purpose is only to act as the
652 * start/end of the circular linked list of NativeIterators in
653 * ObjectRealm::enumerators.
654 */
NativeIterator()655 NativeIterator::NativeIterator() {
656 // Do our best to enforce that nothing in |this| except the two fields set
657 // below is ever observed.
658 AlwaysPoison(static_cast<void*>(this), JS_NEW_NATIVE_ITERATOR_PATTERN,
659 sizeof(*this), MemCheckKind::MakeUndefined);
660
661 // These are the only two fields in sentinel NativeIterators that are
662 // examined, in ObjectRealm::traceWeakNativeIterators. Everything else is
663 // only examined *if* it's a NativeIterator being traced by a
664 // PropertyIteratorObject that owns it, and nothing owns this iterator.
665 prev_ = next_ = this;
666 }
667
allocateSentinel(JSContext * cx)668 NativeIterator* NativeIterator::allocateSentinel(JSContext* cx) {
669 NativeIterator* ni = js_new<NativeIterator>();
670 if (!ni) {
671 ReportOutOfMemory(cx);
672 return nullptr;
673 }
674
675 return ni;
676 }
677
HashIteratorShape(Shape * shape)678 static HashNumber HashIteratorShape(Shape* shape) {
679 return DefaultHasher<Shape*>::hash(shape);
680 }
681
682 /**
683 * Initialize a fresh NativeIterator.
684 *
685 * This definition is a bit tricky: some parts of initializing are fallible, so
686 * as we initialize, we must carefully keep this in GC-safe state (see
687 * NativeIterator::trace).
688 */
NativeIterator(JSContext * cx,Handle<PropertyIteratorObject * > propIter,Handle<JSObject * > objBeingIterated,HandleIdVector props,uint32_t numShapes,HashNumber shapesHash,bool * hadError)689 NativeIterator::NativeIterator(JSContext* cx,
690 Handle<PropertyIteratorObject*> propIter,
691 Handle<JSObject*> objBeingIterated,
692 HandleIdVector props, uint32_t numShapes,
693 HashNumber shapesHash, bool* hadError)
694 : objectBeingIterated_(objBeingIterated),
695 iterObj_(propIter),
696 // NativeIterator initially acts (before full initialization) as if it
697 // contains no shapes...
698 shapesEnd_(shapesBegin()),
699 // ...and no properties.
700 propertyCursor_(
701 reinterpret_cast<GCPtrLinearString*>(shapesBegin() + numShapes)),
702 propertiesEnd_(propertyCursor_),
703 shapesHash_(shapesHash),
704 flagsAndCount_(
705 initialFlagsAndCount(props.length())) // note: no Flags::Initialized
706 {
707 // If there are shapes, the object and all objects on its prototype chain must
708 // be native objects. See CanCompareIterableObjectToCache.
709 MOZ_ASSERT_IF(numShapes > 0,
710 objBeingIterated && objBeingIterated->is<NativeObject>());
711
712 MOZ_ASSERT(!*hadError);
713
714 // NOTE: This must be done first thing: The caller can't free `this` on error
715 // because it has GCPtr fields whose barriers have already fired; the
716 // store buffer has pointers to them. Only the GC can free `this` (via
717 // PropertyIteratorObject::finalize).
718 propIter->setNativeIterator(this);
719
720 // The GC asserts on finalization that `this->allocationSize()` matches the
721 // `nbytes` passed to `AddCellMemory`. So once these lines run, we must make
722 // `this->allocationSize()` correct. That means infallibly initializing the
723 // shapes. It's OK for the constructor to fail after that.
724 size_t nbytes = AllocationSize(props.length(), numShapes);
725 AddCellMemory(propIter, nbytes, MemoryUse::NativeIterator);
726
727 if (numShapes > 0) {
728 // Construct shapes into the shapes array. Also recompute the shapesHash,
729 // which incorporates Shape* addresses that could have changed during a GC
730 // triggered in (among other places) |IdToString| above.
731 JSObject* pobj = objBeingIterated;
732 #ifdef DEBUG
733 uint32_t i = 0;
734 #endif
735 HashNumber shapesHash = 0;
736 do {
737 MOZ_ASSERT(pobj->is<NativeObject>());
738 Shape* shape = pobj->shape();
739 new (shapesEnd_) GCPtrShape(shape);
740 shapesEnd_++;
741 #ifdef DEBUG
742 i++;
743 #endif
744
745 shapesHash = mozilla::AddToHash(shapesHash, HashIteratorShape(shape));
746
747 // The one caller of this method that passes |numShapes > 0|, does
748 // so only if the entire chain consists of cacheable objects (that
749 // necessarily have static prototypes).
750 pobj = pobj->staticPrototype();
751 } while (pobj);
752
753 shapesHash_ = shapesHash;
754 MOZ_ASSERT(i == numShapes);
755 }
756 MOZ_ASSERT(static_cast<void*>(shapesEnd_) == propertyCursor_);
757
758 for (size_t i = 0, len = props.length(); i < len; i++) {
759 JSLinearString* str = IdToString(cx, props[i]);
760 if (!str) {
761 *hadError = true;
762 return;
763 }
764 new (propertiesEnd_) GCPtrLinearString(str);
765 propertiesEnd_++;
766 }
767
768 markInitialized();
769
770 MOZ_ASSERT(!*hadError);
771 }
772
allocationSize() const773 inline size_t NativeIterator::allocationSize() const {
774 size_t numShapes = shapesEnd() - shapesBegin();
775 return AllocationSize(initialPropertyCount(), numShapes);
776 }
777
778 /* static */
match(PropertyIteratorObject * obj,const Lookup & lookup)779 bool IteratorHashPolicy::match(PropertyIteratorObject* obj,
780 const Lookup& lookup) {
781 NativeIterator* ni = obj->getNativeIterator();
782 if (ni->shapesHash() != lookup.shapesHash ||
783 ni->shapeCount() != lookup.numShapes) {
784 return false;
785 }
786
787 return ArrayEqual(reinterpret_cast<Shape**>(ni->shapesBegin()), lookup.shapes,
788 ni->shapeCount());
789 }
790
CanCompareIterableObjectToCache(JSObject * obj)791 static inline bool CanCompareIterableObjectToCache(JSObject* obj) {
792 if (obj->is<NativeObject>()) {
793 return obj->as<NativeObject>().getDenseInitializedLength() == 0;
794 }
795 return false;
796 }
797
LookupInIteratorCache(JSContext * cx,JSObject * obj,uint32_t * numShapes)798 static MOZ_ALWAYS_INLINE PropertyIteratorObject* LookupInIteratorCache(
799 JSContext* cx, JSObject* obj, uint32_t* numShapes) {
800 MOZ_ASSERT(*numShapes == 0);
801
802 Vector<Shape*, 8> shapes(cx);
803 HashNumber shapesHash = 0;
804 JSObject* pobj = obj;
805 do {
806 if (!CanCompareIterableObjectToCache(pobj)) {
807 return nullptr;
808 }
809
810 MOZ_ASSERT(pobj->is<NativeObject>());
811 Shape* shape = pobj->shape();
812 shapesHash = mozilla::AddToHash(shapesHash, HashIteratorShape(shape));
813
814 if (MOZ_UNLIKELY(!shapes.append(shape))) {
815 cx->recoverFromOutOfMemory();
816 return nullptr;
817 }
818
819 pobj = pobj->staticPrototype();
820 } while (pobj);
821
822 MOZ_ASSERT(!shapes.empty());
823 *numShapes = shapes.length();
824
825 IteratorHashPolicy::Lookup lookup(shapes.begin(), shapes.length(),
826 shapesHash);
827 auto p = ObjectRealm::get(obj).iteratorCache.lookup(lookup);
828 if (!p) {
829 return nullptr;
830 }
831
832 PropertyIteratorObject* iterobj = *p;
833 MOZ_ASSERT(iterobj->compartment() == cx->compartment());
834
835 NativeIterator* ni = iterobj->getNativeIterator();
836 if (!ni->isReusable()) {
837 return nullptr;
838 }
839
840 return iterobj;
841 }
842
CanStoreInIteratorCache(JSObject * obj)843 static bool CanStoreInIteratorCache(JSObject* obj) {
844 do {
845 MOZ_ASSERT(obj->as<NativeObject>().getDenseInitializedLength() == 0);
846
847 // Typed arrays have indexed properties not captured by the Shape guard.
848 // Enumerate hooks may add extra properties.
849 const JSClass* clasp = obj->getClass();
850 if (MOZ_UNLIKELY(IsTypedArrayClass(clasp))) {
851 return false;
852 }
853 if (MOZ_UNLIKELY(clasp->getNewEnumerate() || clasp->getEnumerate())) {
854 return false;
855 }
856
857 obj = obj->staticPrototype();
858 } while (obj);
859
860 return true;
861 }
862
StoreInIteratorCache(JSContext * cx,JSObject * obj,PropertyIteratorObject * iterobj)863 [[nodiscard]] static bool StoreInIteratorCache(
864 JSContext* cx, JSObject* obj, PropertyIteratorObject* iterobj) {
865 MOZ_ASSERT(CanStoreInIteratorCache(obj));
866
867 NativeIterator* ni = iterobj->getNativeIterator();
868 MOZ_ASSERT(ni->shapeCount() > 0);
869
870 IteratorHashPolicy::Lookup lookup(
871 reinterpret_cast<Shape**>(ni->shapesBegin()), ni->shapeCount(),
872 ni->shapesHash());
873
874 ObjectRealm::IteratorCache& cache = ObjectRealm::get(obj).iteratorCache;
875 bool ok;
876 auto p = cache.lookupForAdd(lookup);
877 if (MOZ_LIKELY(!p)) {
878 ok = cache.add(p, iterobj);
879 } else {
880 // If we weren't able to use an existing cached iterator, just
881 // replace it.
882 cache.remove(p);
883 ok = cache.relookupOrAdd(p, lookup, iterobj);
884 }
885 if (!ok) {
886 ReportOutOfMemory(cx);
887 return false;
888 }
889
890 return true;
891 }
892
EnumerateProperties(JSContext * cx,HandleObject obj,MutableHandleIdVector props)893 bool js::EnumerateProperties(JSContext* cx, HandleObject obj,
894 MutableHandleIdVector props) {
895 MOZ_ASSERT(props.empty());
896
897 if (MOZ_UNLIKELY(obj->is<ProxyObject>())) {
898 return Proxy::enumerate(cx, obj, props);
899 }
900
901 return Snapshot(cx, obj, 0, props);
902 }
903
904 #ifdef DEBUG
PrototypeMayHaveIndexedProperties(NativeObject * nobj)905 static bool PrototypeMayHaveIndexedProperties(NativeObject* nobj) {
906 JSObject* proto = nobj->staticPrototype();
907 if (!proto) {
908 return false;
909 }
910
911 if (proto->is<NativeObject>() &&
912 proto->as<NativeObject>().getDenseInitializedLength() > 0) {
913 return true;
914 }
915
916 return ObjectMayHaveExtraIndexedProperties(proto);
917 }
918 #endif
919
GetIterator(JSContext * cx,HandleObject obj)920 static JSObject* GetIterator(JSContext* cx, HandleObject obj) {
921 MOZ_ASSERT(!obj->is<PropertyIteratorObject>());
922 MOZ_ASSERT(cx->compartment() == obj->compartment(),
923 "We may end up allocating shapes in the wrong zone!");
924
925 uint32_t numShapes = 0;
926 if (PropertyIteratorObject* iterobj =
927 LookupInIteratorCache(cx, obj, &numShapes)) {
928 NativeIterator* ni = iterobj->getNativeIterator();
929 ni->changeObjectBeingIterated(*obj);
930 RegisterEnumerator(ObjectRealm::get(obj), ni);
931 return iterobj;
932 }
933
934 if (numShapes > 0 && !CanStoreInIteratorCache(obj)) {
935 numShapes = 0;
936 }
937
938 RootedIdVector keys(cx);
939 if (!EnumerateProperties(cx, obj, &keys)) {
940 return nullptr;
941 }
942
943 // If the object has dense elements, mark the dense elements as
944 // maybe-in-iteration.
945 //
946 // The iterator is a snapshot so if indexed properties are added after this
947 // point we don't need to do anything. However, the object might have sparse
948 // elements now that can be densified later. To account for this, we set the
949 // maybe-in-iteration flag also in NativeObject::maybeDensifySparseElements.
950 //
951 // In debug builds, AssertDenseElementsNotIterated is used to check the flag
952 // is set correctly.
953 if (obj->is<NativeObject>() &&
954 obj->as<NativeObject>().getDenseInitializedLength() > 0) {
955 obj->as<NativeObject>().markDenseElementsMaybeInIteration();
956 }
957
958 PropertyIteratorObject* iterobj =
959 CreatePropertyIterator(cx, obj, keys, numShapes, 0);
960 if (!iterobj) {
961 return nullptr;
962 }
963
964 cx->check(iterobj);
965
966 #ifdef DEBUG
967 if (obj->is<NativeObject>()) {
968 if (PrototypeMayHaveIndexedProperties(&obj->as<NativeObject>())) {
969 iterobj->getNativeIterator()->setMaybeHasIndexedPropertiesFromProto();
970 }
971 }
972 #endif
973
974 // Cache the iterator object.
975 if (numShapes > 0) {
976 if (!StoreInIteratorCache(cx, obj, iterobj)) {
977 return nullptr;
978 }
979 }
980
981 return iterobj;
982 }
983
LookupInIteratorCache(JSContext * cx,HandleObject obj)984 PropertyIteratorObject* js::LookupInIteratorCache(JSContext* cx,
985 HandleObject obj) {
986 uint32_t numShapes = 0;
987 return LookupInIteratorCache(cx, obj, &numShapes);
988 }
989
990 // ES 2017 draft 7.4.7.
CreateIterResultObject(JSContext * cx,HandleValue value,bool done)991 PlainObject* js::CreateIterResultObject(JSContext* cx, HandleValue value,
992 bool done) {
993 // Step 1 (implicit).
994
995 // Step 2.
996 Rooted<PlainObject*> templateObject(
997 cx, cx->realm()->getOrCreateIterResultTemplateObject(cx));
998 if (!templateObject) {
999 return nullptr;
1000 }
1001
1002 PlainObject* resultObj;
1003 JS_TRY_VAR_OR_RETURN_NULL(
1004 cx, resultObj, PlainObject::createWithTemplate(cx, templateObject));
1005
1006 // Step 3.
1007 resultObj->setSlot(Realm::IterResultObjectValueSlot, value);
1008
1009 // Step 4.
1010 resultObj->setSlot(Realm::IterResultObjectDoneSlot,
1011 done ? TrueHandleValue : FalseHandleValue);
1012
1013 // Step 5.
1014 return resultObj;
1015 }
1016
getOrCreateIterResultTemplateObject(JSContext * cx)1017 PlainObject* Realm::getOrCreateIterResultTemplateObject(JSContext* cx) {
1018 MOZ_ASSERT(cx->realm() == this);
1019
1020 if (iterResultTemplate_) {
1021 return iterResultTemplate_;
1022 }
1023
1024 PlainObject* templateObj =
1025 createIterResultTemplateObject(cx, WithObjectPrototype::Yes);
1026 iterResultTemplate_.set(templateObj);
1027 return iterResultTemplate_;
1028 }
1029
getOrCreateIterResultWithoutPrototypeTemplateObject(JSContext * cx)1030 PlainObject* Realm::getOrCreateIterResultWithoutPrototypeTemplateObject(
1031 JSContext* cx) {
1032 MOZ_ASSERT(cx->realm() == this);
1033
1034 if (iterResultWithoutPrototypeTemplate_) {
1035 return iterResultWithoutPrototypeTemplate_;
1036 }
1037
1038 PlainObject* templateObj =
1039 createIterResultTemplateObject(cx, WithObjectPrototype::No);
1040 iterResultWithoutPrototypeTemplate_.set(templateObj);
1041 return iterResultWithoutPrototypeTemplate_;
1042 }
1043
createIterResultTemplateObject(JSContext * cx,WithObjectPrototype withProto)1044 PlainObject* Realm::createIterResultTemplateObject(
1045 JSContext* cx, WithObjectPrototype withProto) {
1046 // Create template plain object
1047 Rooted<PlainObject*> templateObject(
1048 cx, withProto == WithObjectPrototype::Yes
1049 ? NewTenuredBuiltinClassInstance<PlainObject>(cx)
1050 : NewObjectWithGivenProto<PlainObject>(cx, nullptr));
1051 if (!templateObject) {
1052 return nullptr;
1053 }
1054
1055 // Set dummy `value` property
1056 if (!NativeDefineDataProperty(cx, templateObject, cx->names().value,
1057 UndefinedHandleValue, JSPROP_ENUMERATE)) {
1058 return nullptr;
1059 }
1060
1061 // Set dummy `done` property
1062 if (!NativeDefineDataProperty(cx, templateObject, cx->names().done,
1063 TrueHandleValue, JSPROP_ENUMERATE)) {
1064 return nullptr;
1065 }
1066
1067 #ifdef DEBUG
1068 // Make sure that the properties are in the right slots.
1069 ShapePropertyIter<NoGC> iter(templateObject->shape());
1070 MOZ_ASSERT(iter->slot() == Realm::IterResultObjectDoneSlot &&
1071 iter->key() == NameToId(cx->names().done));
1072 iter++;
1073 MOZ_ASSERT(iter->slot() == Realm::IterResultObjectValueSlot &&
1074 iter->key() == NameToId(cx->names().value));
1075 #endif
1076
1077 return templateObject;
1078 }
1079
1080 /*** Iterator objects *******************************************************/
1081
sizeOfMisc(mozilla::MallocSizeOf mallocSizeOf) const1082 size_t PropertyIteratorObject::sizeOfMisc(
1083 mozilla::MallocSizeOf mallocSizeOf) const {
1084 return mallocSizeOf(getPrivate());
1085 }
1086
trace(JSTracer * trc,JSObject * obj)1087 void PropertyIteratorObject::trace(JSTracer* trc, JSObject* obj) {
1088 if (NativeIterator* ni =
1089 obj->as<PropertyIteratorObject>().getNativeIterator()) {
1090 ni->trace(trc);
1091 }
1092 }
1093
finalize(JSFreeOp * fop,JSObject * obj)1094 void PropertyIteratorObject::finalize(JSFreeOp* fop, JSObject* obj) {
1095 if (NativeIterator* ni =
1096 obj->as<PropertyIteratorObject>().getNativeIterator()) {
1097 fop->free_(obj, ni, ni->allocationSize(), MemoryUse::NativeIterator);
1098 }
1099 }
1100
1101 const JSClassOps PropertyIteratorObject::classOps_ = {
1102 nullptr, // addProperty
1103 nullptr, // delProperty
1104 nullptr, // enumerate
1105 nullptr, // newEnumerate
1106 nullptr, // resolve
1107 nullptr, // mayResolve
1108 finalize, // finalize
1109 nullptr, // call
1110 nullptr, // hasInstance
1111 nullptr, // construct
1112 trace, // trace
1113 };
1114
1115 const JSClass PropertyIteratorObject::class_ = {
1116 "Iterator", JSCLASS_HAS_PRIVATE | JSCLASS_BACKGROUND_FINALIZE,
1117 &PropertyIteratorObject::classOps_};
1118
1119 static const JSClass ArrayIteratorPrototypeClass = {"Array Iterator", 0};
1120
1121 enum {
1122 ArrayIteratorSlotIteratedObject,
1123 ArrayIteratorSlotNextIndex,
1124 ArrayIteratorSlotItemKind,
1125 ArrayIteratorSlotCount
1126 };
1127
1128 const JSClass ArrayIteratorObject::class_ = {
1129 "Array Iterator", JSCLASS_HAS_RESERVED_SLOTS(ArrayIteratorSlotCount)};
1130
NewArrayIteratorTemplate(JSContext * cx)1131 ArrayIteratorObject* js::NewArrayIteratorTemplate(JSContext* cx) {
1132 RootedObject proto(
1133 cx, GlobalObject::getOrCreateArrayIteratorPrototype(cx, cx->global()));
1134 if (!proto) {
1135 return nullptr;
1136 }
1137
1138 return NewTenuredObjectWithGivenProto<ArrayIteratorObject>(cx, proto);
1139 }
1140
NewArrayIterator(JSContext * cx)1141 ArrayIteratorObject* js::NewArrayIterator(JSContext* cx) {
1142 RootedObject proto(
1143 cx, GlobalObject::getOrCreateArrayIteratorPrototype(cx, cx->global()));
1144 if (!proto) {
1145 return nullptr;
1146 }
1147
1148 return NewObjectWithGivenProto<ArrayIteratorObject>(cx, proto);
1149 }
1150
1151 static const JSFunctionSpec array_iterator_methods[] = {
1152 JS_SELF_HOSTED_FN("next", "ArrayIteratorNext", 0, 0), JS_FS_END};
1153
1154 static const JSClass StringIteratorPrototypeClass = {"String Iterator", 0};
1155
1156 enum {
1157 StringIteratorSlotIteratedObject,
1158 StringIteratorSlotNextIndex,
1159 StringIteratorSlotCount
1160 };
1161
1162 const JSClass StringIteratorObject::class_ = {
1163 "String Iterator", JSCLASS_HAS_RESERVED_SLOTS(StringIteratorSlotCount)};
1164
1165 static const JSFunctionSpec string_iterator_methods[] = {
1166 JS_SELF_HOSTED_FN("next", "StringIteratorNext", 0, 0), JS_FS_END};
1167
NewStringIteratorTemplate(JSContext * cx)1168 StringIteratorObject* js::NewStringIteratorTemplate(JSContext* cx) {
1169 RootedObject proto(
1170 cx, GlobalObject::getOrCreateStringIteratorPrototype(cx, cx->global()));
1171 if (!proto) {
1172 return nullptr;
1173 }
1174
1175 return NewTenuredObjectWithGivenProto<StringIteratorObject>(cx, proto);
1176 }
1177
NewStringIterator(JSContext * cx)1178 StringIteratorObject* js::NewStringIterator(JSContext* cx) {
1179 RootedObject proto(
1180 cx, GlobalObject::getOrCreateStringIteratorPrototype(cx, cx->global()));
1181 if (!proto) {
1182 return nullptr;
1183 }
1184
1185 return NewObjectWithGivenProto<StringIteratorObject>(cx, proto);
1186 }
1187
1188 static const JSClass RegExpStringIteratorPrototypeClass = {
1189 "RegExp String Iterator", 0};
1190
1191 enum {
1192 // The regular expression used for iteration. May hold the original RegExp
1193 // object when it is reused instead of a new RegExp object.
1194 RegExpStringIteratorSlotRegExp,
1195
1196 // The String value being iterated upon.
1197 RegExpStringIteratorSlotString,
1198
1199 // The source string of the original RegExp object. Used to validate we can
1200 // reuse the original RegExp object for matching.
1201 RegExpStringIteratorSlotSource,
1202
1203 // The flags of the original RegExp object.
1204 RegExpStringIteratorSlotFlags,
1205
1206 // When non-negative, this slot holds the current lastIndex position when
1207 // reusing the original RegExp object for matching. When set to |-1|, the
1208 // iterator has finished. When set to any other negative value, the
1209 // iterator is not yet exhausted and we're not on the fast path and we're
1210 // not reusing the input RegExp object.
1211 RegExpStringIteratorSlotLastIndex,
1212
1213 RegExpStringIteratorSlotCount
1214 };
1215
1216 static_assert(RegExpStringIteratorSlotRegExp ==
1217 REGEXP_STRING_ITERATOR_REGEXP_SLOT,
1218 "RegExpStringIteratorSlotRegExp must match self-hosting define "
1219 "for regexp slot.");
1220 static_assert(RegExpStringIteratorSlotString ==
1221 REGEXP_STRING_ITERATOR_STRING_SLOT,
1222 "RegExpStringIteratorSlotString must match self-hosting define "
1223 "for string slot.");
1224 static_assert(RegExpStringIteratorSlotSource ==
1225 REGEXP_STRING_ITERATOR_SOURCE_SLOT,
1226 "RegExpStringIteratorSlotString must match self-hosting define "
1227 "for source slot.");
1228 static_assert(RegExpStringIteratorSlotFlags ==
1229 REGEXP_STRING_ITERATOR_FLAGS_SLOT,
1230 "RegExpStringIteratorSlotFlags must match self-hosting define "
1231 "for flags slot.");
1232 static_assert(RegExpStringIteratorSlotLastIndex ==
1233 REGEXP_STRING_ITERATOR_LASTINDEX_SLOT,
1234 "RegExpStringIteratorSlotLastIndex must match self-hosting "
1235 "define for lastIndex slot.");
1236
1237 const JSClass RegExpStringIteratorObject::class_ = {
1238 "RegExp String Iterator",
1239 JSCLASS_HAS_RESERVED_SLOTS(RegExpStringIteratorSlotCount)};
1240
1241 static const JSFunctionSpec regexp_string_iterator_methods[] = {
1242 JS_SELF_HOSTED_FN("next", "RegExpStringIteratorNext", 0, 0),
1243
1244 JS_FS_END};
1245
NewRegExpStringIteratorTemplate(JSContext * cx)1246 RegExpStringIteratorObject* js::NewRegExpStringIteratorTemplate(JSContext* cx) {
1247 RootedObject proto(cx, GlobalObject::getOrCreateRegExpStringIteratorPrototype(
1248 cx, cx->global()));
1249 if (!proto) {
1250 return nullptr;
1251 }
1252
1253 return NewTenuredObjectWithGivenProto<RegExpStringIteratorObject>(cx, proto);
1254 }
1255
NewRegExpStringIterator(JSContext * cx)1256 RegExpStringIteratorObject* js::NewRegExpStringIterator(JSContext* cx) {
1257 RootedObject proto(cx, GlobalObject::getOrCreateRegExpStringIteratorPrototype(
1258 cx, cx->global()));
1259 if (!proto) {
1260 return nullptr;
1261 }
1262
1263 return NewObjectWithGivenProto<RegExpStringIteratorObject>(cx, proto);
1264 }
1265
ValueToIterator(JSContext * cx,HandleValue vp)1266 JSObject* js::ValueToIterator(JSContext* cx, HandleValue vp) {
1267 RootedObject obj(cx);
1268 if (vp.isObject()) {
1269 /* Common case. */
1270 obj = &vp.toObject();
1271 } else if (vp.isNullOrUndefined()) {
1272 /*
1273 * Enumerating over null and undefined gives an empty enumerator, so
1274 * that |for (var p in <null or undefined>) <loop>;| never executes
1275 * <loop>, per ES5 12.6.4.
1276 */
1277 RootedIdVector props(cx); // Empty
1278 return CreatePropertyIterator(cx, nullptr, props, 0, 0);
1279 } else {
1280 obj = ToObject(cx, vp);
1281 if (!obj) {
1282 return nullptr;
1283 }
1284 }
1285
1286 return GetIterator(cx, obj);
1287 }
1288
CloseIterator(JSObject * obj)1289 void js::CloseIterator(JSObject* obj) {
1290 if (obj->is<PropertyIteratorObject>()) {
1291 /* Remove enumerators from the active list, which is a stack. */
1292 NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator();
1293
1294 ni->unlink();
1295
1296 MOZ_ASSERT(ni->isActive());
1297 ni->markInactive();
1298
1299 // Reset the enumerator; it may still be in the cached iterators for
1300 // this thread and can be reused.
1301 ni->resetPropertyCursorForReuse();
1302 }
1303 }
1304
IteratorCloseForException(JSContext * cx,HandleObject obj)1305 bool js::IteratorCloseForException(JSContext* cx, HandleObject obj) {
1306 MOZ_ASSERT(cx->isExceptionPending());
1307
1308 bool isClosingGenerator = cx->isClosingGenerator();
1309 JS::AutoSaveExceptionState savedExc(cx);
1310
1311 // Implements IteratorClose (ES 7.4.6) for exception unwinding. See
1312 // also the bytecode generated by BytecodeEmitter::emitIteratorClose.
1313
1314 // Step 3.
1315 //
1316 // Get the "return" method.
1317 RootedValue returnMethod(cx);
1318 if (!GetProperty(cx, obj, obj, cx->names().return_, &returnMethod)) {
1319 return false;
1320 }
1321
1322 // Step 4.
1323 //
1324 // Do nothing if "return" is null or undefined. Throw a TypeError if the
1325 // method is not IsCallable.
1326 if (returnMethod.isNullOrUndefined()) {
1327 return true;
1328 }
1329 if (!IsCallable(returnMethod)) {
1330 return ReportIsNotFunction(cx, returnMethod);
1331 }
1332
1333 // Step 5, 6, 8.
1334 //
1335 // Call "return" if it is not null or undefined.
1336 RootedValue rval(cx);
1337 bool ok = Call(cx, returnMethod, obj, &rval);
1338 if (isClosingGenerator) {
1339 // Closing an iterator is implemented as an exception, but in spec
1340 // terms it is a Completion value with [[Type]] return. In this case
1341 // we *do* care if the call threw and if it returned an object.
1342 if (!ok) {
1343 return false;
1344 }
1345 if (!rval.isObject()) {
1346 return ThrowCheckIsObject(cx, CheckIsObjectKind::IteratorReturn);
1347 }
1348 } else {
1349 // We don't care if the call threw or that it returned an Object, as
1350 // Step 6 says if IteratorClose is being called during a throw, the
1351 // original throw has primacy.
1352 savedExc.restore();
1353 }
1354
1355 return true;
1356 }
1357
UnwindIteratorForUncatchableException(JSObject * obj)1358 void js::UnwindIteratorForUncatchableException(JSObject* obj) {
1359 if (obj->is<PropertyIteratorObject>()) {
1360 NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator();
1361 ni->unlink();
1362 }
1363 }
1364
SuppressDeletedProperty(JSContext * cx,NativeIterator * ni,HandleObject obj,Handle<JSLinearString * > str)1365 static bool SuppressDeletedProperty(JSContext* cx, NativeIterator* ni,
1366 HandleObject obj,
1367 Handle<JSLinearString*> str) {
1368 if (ni->objectBeingIterated() != obj) {
1369 return true;
1370 }
1371
1372 // Optimization for the following common case:
1373 //
1374 // for (var p in o) {
1375 // delete o[p];
1376 // }
1377 //
1378 // Note that usually both strings will be atoms so we only check for pointer
1379 // equality here.
1380 if (ni->previousPropertyWas(str)) {
1381 return true;
1382 }
1383
1384 while (true) {
1385 bool restart = false;
1386
1387 // Check whether id is still to come.
1388 GCPtrLinearString* const cursor = ni->nextProperty();
1389 GCPtrLinearString* const end = ni->propertiesEnd();
1390 for (GCPtrLinearString* idp = cursor; idp < end; ++idp) {
1391 // Common case: both strings are atoms.
1392 if ((*idp)->isAtom() && str->isAtom()) {
1393 if (*idp != str) {
1394 continue;
1395 }
1396 } else {
1397 if (!EqualStrings(*idp, str)) {
1398 continue;
1399 }
1400 }
1401
1402 // Check whether another property along the prototype chain became
1403 // visible as a result of this deletion.
1404 RootedObject proto(cx);
1405 if (!GetPrototype(cx, obj, &proto)) {
1406 return false;
1407 }
1408 if (proto) {
1409 RootedId id(cx);
1410 RootedValue idv(cx, StringValue(*idp));
1411 if (!PrimitiveValueToId<CanGC>(cx, idv, &id)) {
1412 return false;
1413 }
1414
1415 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
1416 RootedObject holder(cx);
1417 if (!GetPropertyDescriptor(cx, proto, id, &desc, &holder)) {
1418 return false;
1419 }
1420
1421 if (desc.isSome() && desc->enumerable()) {
1422 continue;
1423 }
1424 }
1425
1426 // If GetPropertyDescriptor above removed a property from ni, start
1427 // over.
1428 if (end != ni->propertiesEnd() || cursor != ni->nextProperty()) {
1429 restart = true;
1430 break;
1431 }
1432
1433 // No property along the prototype chain stepped in to take the
1434 // property's place, so go ahead and delete id from the list.
1435 // If it is the next property to be enumerated, just skip it.
1436 if (idp == cursor) {
1437 ni->incCursor();
1438 } else {
1439 for (GCPtrLinearString* p = idp; p + 1 != end; p++) {
1440 *p = *(p + 1);
1441 }
1442
1443 ni->trimLastProperty();
1444 }
1445
1446 ni->markHasUnvisitedPropertyDeletion();
1447 return true;
1448 }
1449
1450 if (!restart) {
1451 return true;
1452 }
1453 }
1454 }
1455
1456 /*
1457 * Suppress enumeration of deleted properties. This function must be called
1458 * when a property is deleted and there might be active enumerators.
1459 *
1460 * We maintain a list of active non-escaping for-in enumerators. To suppress
1461 * a property, we check whether each active enumerator contains the (obj, id)
1462 * pair and has not yet enumerated |id|. If so, and |id| is the next property,
1463 * we simply advance the cursor. Otherwise, we delete |id| from the list.
1464 *
1465 * We do not suppress enumeration of a property deleted along an object's
1466 * prototype chain. Only direct deletions on the object are handled.
1467 */
SuppressDeletedPropertyHelper(JSContext * cx,HandleObject obj,Handle<JSLinearString * > str)1468 static bool SuppressDeletedPropertyHelper(JSContext* cx, HandleObject obj,
1469 Handle<JSLinearString*> str) {
1470 NativeIterator* enumeratorList = ObjectRealm::get(obj).enumerators;
1471 NativeIterator* ni = enumeratorList->next();
1472
1473 while (ni != enumeratorList) {
1474 if (!SuppressDeletedProperty(cx, ni, obj, str)) {
1475 return false;
1476 }
1477 ni = ni->next();
1478 }
1479
1480 return true;
1481 }
1482
SuppressDeletedProperty(JSContext * cx,HandleObject obj,jsid id)1483 bool js::SuppressDeletedProperty(JSContext* cx, HandleObject obj, jsid id) {
1484 if (MOZ_LIKELY(!ObjectRealm::get(obj).objectMaybeInIteration(obj))) {
1485 return true;
1486 }
1487
1488 if (id.isSymbol()) {
1489 return true;
1490 }
1491
1492 Rooted<JSLinearString*> str(cx, IdToString(cx, id));
1493 if (!str) {
1494 return false;
1495 }
1496 return SuppressDeletedPropertyHelper(cx, obj, str);
1497 }
1498
SuppressDeletedElement(JSContext * cx,HandleObject obj,uint32_t index)1499 bool js::SuppressDeletedElement(JSContext* cx, HandleObject obj,
1500 uint32_t index) {
1501 if (MOZ_LIKELY(!ObjectRealm::get(obj).objectMaybeInIteration(obj))) {
1502 return true;
1503 }
1504
1505 RootedId id(cx);
1506 if (!IndexToId(cx, index, &id)) {
1507 return false;
1508 }
1509
1510 Rooted<JSLinearString*> str(cx, IdToString(cx, id));
1511 if (!str) {
1512 return false;
1513 }
1514 return SuppressDeletedPropertyHelper(cx, obj, str);
1515 }
1516
1517 #ifdef DEBUG
AssertDenseElementsNotIterated(NativeObject * obj)1518 void js::AssertDenseElementsNotIterated(NativeObject* obj) {
1519 // Search for active iterators for |obj| and assert they don't contain any
1520 // property keys that are dense elements. This is used to check correctness
1521 // of the MAYBE_IN_ITERATION flag on ObjectElements.
1522 //
1523 // Ignore iterators that may contain indexed properties from objects on the
1524 // prototype chain, as that can result in false positives. See bug 1656744.
1525
1526 // Limit the number of properties we check to avoid slowing down debug builds
1527 // too much.
1528 static constexpr uint32_t MaxPropsToCheck = 10;
1529 uint32_t propsChecked = 0;
1530
1531 NativeIterator* enumeratorList = ObjectRealm::get(obj).enumerators;
1532 NativeIterator* ni = enumeratorList->next();
1533
1534 while (ni != enumeratorList) {
1535 if (ni->objectBeingIterated() == obj &&
1536 !ni->maybeHasIndexedPropertiesFromProto()) {
1537 for (GCPtrLinearString* idp = ni->nextProperty();
1538 idp < ni->propertiesEnd(); ++idp) {
1539 uint32_t index;
1540 if (idp->get()->isIndex(&index)) {
1541 MOZ_ASSERT(!obj->containsDenseElement(index));
1542 }
1543 if (++propsChecked > MaxPropsToCheck) {
1544 return;
1545 }
1546 }
1547 }
1548 ni = ni->next();
1549 }
1550 }
1551 #endif
1552
1553 static const JSFunctionSpec iterator_methods[] = {
1554 JS_SELF_HOSTED_SYM_FN(iterator, "IteratorIdentity", 0, 0), JS_FS_END};
1555
1556 static const JSFunctionSpec iterator_static_methods[] = {
1557 JS_SELF_HOSTED_FN("from", "IteratorFrom", 1, 0), JS_FS_END};
1558
1559 // These methods are only attached to Iterator.prototype when the
1560 // Iterator Helpers feature is enabled.
1561 static const JSFunctionSpec iterator_methods_with_helpers[] = {
1562 JS_SELF_HOSTED_FN("map", "IteratorMap", 1, 0),
1563 JS_SELF_HOSTED_FN("filter", "IteratorFilter", 1, 0),
1564 JS_SELF_HOSTED_FN("take", "IteratorTake", 1, 0),
1565 JS_SELF_HOSTED_FN("drop", "IteratorDrop", 1, 0),
1566 JS_SELF_HOSTED_FN("asIndexedPairs", "IteratorAsIndexedPairs", 0, 0),
1567 JS_SELF_HOSTED_FN("flatMap", "IteratorFlatMap", 1, 0),
1568 JS_SELF_HOSTED_FN("reduce", "IteratorReduce", 1, 0),
1569 JS_SELF_HOSTED_FN("toArray", "IteratorToArray", 0, 0),
1570 JS_SELF_HOSTED_FN("forEach", "IteratorForEach", 1, 0),
1571 JS_SELF_HOSTED_FN("some", "IteratorSome", 1, 0),
1572 JS_SELF_HOSTED_FN("every", "IteratorEvery", 1, 0),
1573 JS_SELF_HOSTED_FN("find", "IteratorFind", 1, 0),
1574 JS_SELF_HOSTED_SYM_FN(iterator, "IteratorIdentity", 0, 0),
1575 JS_FS_END};
1576
1577 /* static */
initIteratorProto(JSContext * cx,Handle<GlobalObject * > global)1578 bool GlobalObject::initIteratorProto(JSContext* cx,
1579 Handle<GlobalObject*> global) {
1580 if (global->getReservedSlot(ITERATOR_PROTO).isObject()) {
1581 return true;
1582 }
1583
1584 RootedObject proto(
1585 cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
1586 if (!proto) {
1587 return false;
1588 }
1589
1590 // %IteratorPrototype%.map.[[Prototype]] is %Generator% and
1591 // %Generator%.prototype.[[Prototype]] is %IteratorPrototype%.
1592 // Populate the slot early, to prevent runaway mutual recursion.
1593 global->setReservedSlot(ITERATOR_PROTO, ObjectValue(*proto));
1594
1595 if (!DefinePropertiesAndFunctions(cx, proto, nullptr, iterator_methods)) {
1596 // In this case, we leave a partially initialized object in the
1597 // slot. There's no obvious way to do better, since this object may already
1598 // be in the prototype chain of %GeneratorPrototype%.
1599 return false;
1600 }
1601
1602 return true;
1603 }
1604
1605 /* static */
1606 template <unsigned Slot, const JSClass* ProtoClass,
1607 const JSFunctionSpec* Methods>
initObjectIteratorProto(JSContext * cx,Handle<GlobalObject * > global,HandleAtom tag)1608 bool GlobalObject::initObjectIteratorProto(JSContext* cx,
1609 Handle<GlobalObject*> global,
1610 HandleAtom tag) {
1611 if (global->getReservedSlot(Slot).isObject()) {
1612 return true;
1613 }
1614
1615 RootedObject iteratorProto(
1616 cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
1617 if (!iteratorProto) {
1618 return false;
1619 }
1620
1621 RootedObject proto(cx, GlobalObject::createBlankPrototypeInheriting(
1622 cx, ProtoClass, iteratorProto));
1623 if (!proto || !DefinePropertiesAndFunctions(cx, proto, nullptr, Methods) ||
1624 (tag && !DefineToStringTag(cx, proto, tag))) {
1625 return false;
1626 }
1627
1628 global->setReservedSlot(Slot, ObjectValue(*proto));
1629 return true;
1630 }
1631
1632 /* static */
getOrCreateArrayIteratorPrototype(JSContext * cx,Handle<GlobalObject * > global)1633 NativeObject* GlobalObject::getOrCreateArrayIteratorPrototype(
1634 JSContext* cx, Handle<GlobalObject*> global) {
1635 return MaybeNativeObject(getOrCreateObject(
1636 cx, global, ARRAY_ITERATOR_PROTO, cx->names().ArrayIterator.toHandle(),
1637 initObjectIteratorProto<ARRAY_ITERATOR_PROTO,
1638 &ArrayIteratorPrototypeClass,
1639 array_iterator_methods>));
1640 }
1641
1642 /* static */
getOrCreateStringIteratorPrototype(JSContext * cx,Handle<GlobalObject * > global)1643 JSObject* GlobalObject::getOrCreateStringIteratorPrototype(
1644 JSContext* cx, Handle<GlobalObject*> global) {
1645 return getOrCreateObject(
1646 cx, global, STRING_ITERATOR_PROTO, cx->names().StringIterator.toHandle(),
1647 initObjectIteratorProto<STRING_ITERATOR_PROTO,
1648 &StringIteratorPrototypeClass,
1649 string_iterator_methods>);
1650 }
1651
1652 /* static */
getOrCreateRegExpStringIteratorPrototype(JSContext * cx,Handle<GlobalObject * > global)1653 JSObject* GlobalObject::getOrCreateRegExpStringIteratorPrototype(
1654 JSContext* cx, Handle<GlobalObject*> global) {
1655 return getOrCreateObject(
1656 cx, global, REGEXP_STRING_ITERATOR_PROTO,
1657 cx->names().RegExpStringIterator.toHandle(),
1658 initObjectIteratorProto<REGEXP_STRING_ITERATOR_PROTO,
1659 &RegExpStringIteratorPrototypeClass,
1660 regexp_string_iterator_methods>);
1661 }
1662
1663 // Iterator Helper Proposal 2.1.3.1 Iterator()
1664 // https://tc39.es/proposal-iterator-helpers/#sec-iterator as of revision
1665 // ed6e15a
IteratorConstructor(JSContext * cx,unsigned argc,Value * vp)1666 static bool IteratorConstructor(JSContext* cx, unsigned argc, Value* vp) {
1667 CallArgs args = CallArgsFromVp(argc, vp);
1668
1669 // Step 1.
1670 if (!ThrowIfNotConstructing(cx, args, js_Iterator_str)) {
1671 return false;
1672 }
1673 // Throw TypeError if NewTarget is the active function object, preventing the
1674 // Iterator constructor from being used directly.
1675 if (args.callee() == args.newTarget().toObject()) {
1676 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1677 JSMSG_BOGUS_CONSTRUCTOR, js_Iterator_str);
1678 return false;
1679 }
1680
1681 // Step 2.
1682 RootedObject proto(cx);
1683 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Iterator, &proto)) {
1684 return false;
1685 }
1686
1687 JSObject* obj = NewObjectWithClassProto<IteratorObject>(cx, proto);
1688 if (!obj) {
1689 return false;
1690 }
1691
1692 args.rval().setObject(*obj);
1693 return true;
1694 }
1695
1696 static const ClassSpec IteratorObjectClassSpec = {
1697 GenericCreateConstructor<IteratorConstructor, 0, gc::AllocKind::FUNCTION>,
1698 GenericCreatePrototype<IteratorObject>,
1699 iterator_static_methods,
1700 nullptr,
1701 iterator_methods_with_helpers,
1702 nullptr,
1703 nullptr,
1704 };
1705
1706 const JSClass IteratorObject::class_ = {
1707 js_Iterator_str,
1708 JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator),
1709 JS_NULL_CLASS_OPS,
1710 &IteratorObjectClassSpec,
1711 };
1712
1713 const JSClass IteratorObject::protoClass_ = {
1714 "Iterator.prototype",
1715 JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator),
1716 JS_NULL_CLASS_OPS,
1717 &IteratorObjectClassSpec,
1718 };
1719
1720 // Set up WrapForValidIteratorObject class and its prototype.
1721 static const JSFunctionSpec wrap_for_valid_iterator_methods[] = {
1722 JS_SELF_HOSTED_FN("next", "WrapForValidIteratorNext", 1, 0),
1723 JS_SELF_HOSTED_FN("return", "WrapForValidIteratorReturn", 1, 0),
1724 JS_SELF_HOSTED_FN("throw", "WrapForValidIteratorThrow", 1, 0),
1725 JS_FS_END,
1726 };
1727
1728 static const JSClass WrapForValidIteratorPrototypeClass = {
1729 "Wrap For Valid Iterator", 0};
1730
1731 const JSClass WrapForValidIteratorObject::class_ = {
1732 "Wrap For Valid Iterator",
1733 JSCLASS_HAS_RESERVED_SLOTS(WrapForValidIteratorObject::SlotCount),
1734 };
1735
1736 /* static */
getOrCreateWrapForValidIteratorPrototype(JSContext * cx,Handle<GlobalObject * > global)1737 NativeObject* GlobalObject::getOrCreateWrapForValidIteratorPrototype(
1738 JSContext* cx, Handle<GlobalObject*> global) {
1739 return MaybeNativeObject(getOrCreateObject(
1740 cx, global, WRAP_FOR_VALID_ITERATOR_PROTO, HandleAtom(nullptr),
1741 initObjectIteratorProto<WRAP_FOR_VALID_ITERATOR_PROTO,
1742 &WrapForValidIteratorPrototypeClass,
1743 wrap_for_valid_iterator_methods>));
1744 }
1745
NewWrapForValidIterator(JSContext * cx)1746 WrapForValidIteratorObject* js::NewWrapForValidIterator(JSContext* cx) {
1747 RootedObject proto(cx, GlobalObject::getOrCreateWrapForValidIteratorPrototype(
1748 cx, cx->global()));
1749 if (!proto) {
1750 return nullptr;
1751 }
1752 return NewObjectWithGivenProto<WrapForValidIteratorObject>(cx, proto);
1753 }
1754
1755 // Common iterator object returned by Iterator Helper methods.
1756 static const JSFunctionSpec iterator_helper_methods[] = {
1757 JS_SELF_HOSTED_FN("next", "IteratorHelperNext", 1, 0),
1758 JS_SELF_HOSTED_FN("return", "IteratorHelperReturn", 1, 0),
1759 JS_SELF_HOSTED_FN("throw", "IteratorHelperThrow", 1, 0), JS_FS_END};
1760
1761 static const JSClass IteratorHelperPrototypeClass = {"Iterator Helper", 0};
1762
1763 const JSClass IteratorHelperObject::class_ = {
1764 "Iterator Helper",
1765 JSCLASS_HAS_RESERVED_SLOTS(IteratorHelperObject::SlotCount),
1766 };
1767
1768 /* static */
getOrCreateIteratorHelperPrototype(JSContext * cx,Handle<GlobalObject * > global)1769 NativeObject* GlobalObject::getOrCreateIteratorHelperPrototype(
1770 JSContext* cx, Handle<GlobalObject*> global) {
1771 return MaybeNativeObject(
1772 getOrCreateObject(cx, global, ITERATOR_HELPER_PROTO, HandleAtom(nullptr),
1773 initObjectIteratorProto<ITERATOR_HELPER_PROTO,
1774 &IteratorHelperPrototypeClass,
1775 iterator_helper_methods>));
1776 }
1777
NewIteratorHelper(JSContext * cx)1778 IteratorHelperObject* js::NewIteratorHelper(JSContext* cx) {
1779 RootedObject proto(
1780 cx, GlobalObject::getOrCreateIteratorHelperPrototype(cx, cx->global()));
1781 if (!proto) {
1782 return nullptr;
1783 }
1784 return NewObjectWithGivenProto<IteratorHelperObject>(cx, proto);
1785 }
1786