Iteration.cpp (79070B)
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/Likely.h" 13 #include "mozilla/Maybe.h" 14 #include "mozilla/MemoryReporting.h" 15 #include "mozilla/PodOperations.h" 16 17 #include <algorithm> 18 #include <new> 19 20 #include "jsapi.h" 21 #include "jstypes.h" 22 23 #include "builtin/Array.h" 24 #include "builtin/MapObject.h" 25 #include "builtin/SelfHostingDefines.h" 26 #include "ds/Sort.h" 27 #include "gc/GC.h" 28 #include "gc/GCContext.h" 29 #include "js/ForOfIterator.h" // JS::ForOfIterator 30 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 31 #include "js/PropertySpec.h" 32 #include "util/DifferentialTesting.h" 33 #include "util/Poison.h" 34 #include "vm/GlobalObject.h" 35 #include "vm/Interpreter.h" 36 #include "vm/JSContext.h" 37 #include "vm/JSObject.h" 38 #include "vm/NativeObject.h" // js::PlainObject 39 #include "vm/Shape.h" 40 #include "vm/StringType.h" 41 #include "vm/TypedArrayObject.h" 42 #include "vm/Watchtower.h" 43 44 #include "gc/StoreBuffer-inl.h" 45 #include "vm/NativeObject-inl.h" 46 #include "vm/PlainObject-inl.h" // js::PlainObject::createWithTemplate 47 48 using namespace js; 49 50 using mozilla::ArrayEqual; 51 using mozilla::Maybe; 52 using mozilla::PodCopy; 53 54 using RootedPropertyIteratorObject = Rooted<PropertyIteratorObject*>; 55 56 static const gc::AllocKind ITERATOR_FINALIZE_KIND = 57 gc::AllocKind::OBJECT2_BACKGROUND; 58 59 // Beware! This function may have to trace incompletely-initialized 60 // |NativeIterator| allocations if the |IdToString| in that constructor recurs 61 // into this code. 62 void NativeIterator::trace(JSTracer* trc) { 63 TraceNullableEdge(trc, &objectBeingIterated_, "objectBeingIterated_"); 64 TraceNullableEdge(trc, &iterObj_, "iterObj_"); 65 TraceNullableEdge(trc, &objShape_, "objShape_"); 66 67 // The limits below are correct at every instant of |NativeIterator| 68 // initialization, with the end-pointer incremented as each new shape is 69 // created, so they're safe to use here. 70 std::for_each(protoShapesBegin(allocatedPropertyCount()), protoShapesEnd(), 71 [trc](GCPtr<Shape*>& shape) { 72 TraceEdge(trc, &shape, "iterator_proto_shape"); 73 }); 74 75 std::for_each(propertiesBegin(), propertiesEnd(), 76 [trc](IteratorProperty& prop) { prop.traceString(trc); }); 77 } 78 79 void IteratorProperty::traceString(JSTracer* trc) { 80 // Properties begin life non-null and never *become* null. 81 // Trace the underlying string while preserving the deleted bit. 82 JSLinearString* str = asString(); 83 TraceManuallyBarrieredEdge(trc, &str, "iterator-property-string"); 84 raw_ = uintptr_t(str) | (deleted() ? DeletedBit : 0); 85 } 86 87 using PropertyKeySet = GCHashSet<PropertyKey, DefaultHasher<PropertyKey>>; 88 89 class PropertyEnumerator { 90 RootedObject obj_; 91 MutableHandleIdVector props_; 92 PropertyIndexVector* indices_; 93 94 uint32_t flags_; 95 Rooted<PropertyKeySet> visited_; 96 97 uint32_t ownPropertyCount_; 98 99 bool enumeratingProtoChain_ = false; 100 101 enum class IndicesState { 102 // Every property that has been enumerated so far can be represented as a 103 // PropertyIndex, but we are not currently producing a list of indices. If 104 // the state is Valid when we are done enumerating, then the resulting 105 // iterator can be marked with NativeIterator::Flags::IndicesSupported. 106 Valid, 107 108 // Every property that has been enumerated so far can be represented as a 109 // PropertyIndex, and |indices_| points to a PropertyIndexVector containing 110 // those indices. This is used when we want to create a NativeIterator with 111 // valid indices. 112 Allocating, 113 114 // It is not possible to represent every property of the object being 115 // enumerated as a PropertyIndex. For example, enumerated properties on the 116 // prototype chain are unsupported. We can transition to this state from 117 // either of the other two. 118 Unsupported 119 }; 120 IndicesState indicesState_; 121 122 public: 123 PropertyEnumerator(JSContext* cx, JSObject* obj, uint32_t flags, 124 MutableHandleIdVector props, 125 PropertyIndexVector* indices = nullptr) 126 : obj_(cx, obj), 127 props_(props), 128 indices_(indices), 129 flags_(flags), 130 visited_(cx, PropertyKeySet(cx)), 131 indicesState_(indices ? IndicesState::Allocating 132 : IndicesState::Valid) {} 133 134 bool snapshot(JSContext* cx); 135 136 void markIndicesUnsupported() { indicesState_ = IndicesState::Unsupported; } 137 bool supportsIndices() const { 138 return indicesState_ != IndicesState::Unsupported; 139 } 140 bool allocatingIndices() const { 141 return indicesState_ == IndicesState::Allocating; 142 } 143 uint32_t ownPropertyCount() const { return ownPropertyCount_; } 144 145 private: 146 template <bool CheckForDuplicates> 147 bool enumerate(JSContext* cx, jsid id, bool enumerable, 148 PropertyIndex index = PropertyIndex::Invalid()); 149 150 bool enumerateExtraProperties(JSContext* cx); 151 152 template <bool CheckForDuplicates> 153 bool enumerateNativeProperties(JSContext* cx); 154 155 bool enumerateNativeProperties(JSContext* cx, bool checkForDuplicates) { 156 if (checkForDuplicates) { 157 return enumerateNativeProperties<true>(cx); 158 } 159 return enumerateNativeProperties<false>(cx); 160 } 161 162 template <bool CheckForDuplicates> 163 bool enumerateProxyProperties(JSContext* cx); 164 165 void reversePropsAndIndicesAfter(size_t initialLength) { 166 // We iterate through prop maps in descending order of property creation, 167 // but we need our return value to be in ascending order. If we are tracking 168 // property indices, make sure to keep them in sync. 169 MOZ_ASSERT(props_.begin() + initialLength <= props_.end()); 170 MOZ_ASSERT_IF(allocatingIndices(), props_.length() == indices_->length()); 171 172 std::reverse(props_.begin() + initialLength, props_.end()); 173 if (allocatingIndices()) { 174 std::reverse(indices_->begin() + initialLength, indices_->end()); 175 } 176 } 177 }; 178 179 template <bool CheckForDuplicates> 180 bool PropertyEnumerator::enumerate(JSContext* cx, jsid id, bool enumerable, 181 PropertyIndex index) { 182 if (CheckForDuplicates) { 183 // If we've already seen this, we definitely won't add it. 184 PropertyKeySet::AddPtr p = visited_.lookupForAdd(id); 185 if (MOZ_UNLIKELY(!!p)) { 186 return true; 187 } 188 189 // It's not necessary to add properties to the hash set at the end of 190 // the prototype chain, but custom enumeration behaviors might return 191 // duplicated properties, so always add in such cases. 192 if (obj_->is<ProxyObject>() || obj_->staticPrototype() || 193 obj_->getClass()->getNewEnumerate()) { 194 if (!visited_.add(p, id)) { 195 return false; 196 } 197 } 198 } 199 200 if (!enumerable && !(flags_ & JSITER_HIDDEN)) { 201 return true; 202 } 203 204 // Symbol-keyed properties and nonenumerable properties are skipped unless 205 // the caller specifically asks for them. A caller can also filter out 206 // non-symbols by asking for JSITER_SYMBOLSONLY. PrivateName symbols are 207 // skipped unless JSITER_PRIVATE is passed. 208 if (id.isSymbol()) { 209 if (!(flags_ & JSITER_SYMBOLS)) { 210 return true; 211 } 212 if (!(flags_ & JSITER_PRIVATE) && id.isPrivateName()) { 213 return true; 214 } 215 } else { 216 if ((flags_ & JSITER_SYMBOLSONLY)) { 217 return true; 218 } 219 } 220 221 MOZ_ASSERT_IF(allocatingIndices(), indices_->length() == props_.length()); 222 if (!props_.append(id)) { 223 return false; 224 } 225 226 if (!supportsIndices()) { 227 return true; 228 } 229 if (index.kind() == PropertyIndex::Kind::Invalid || enumeratingProtoChain_) { 230 markIndicesUnsupported(); 231 return true; 232 } 233 234 if (allocatingIndices() && !indices_->append(index)) { 235 return false; 236 } 237 238 return true; 239 } 240 241 bool PropertyEnumerator::enumerateExtraProperties(JSContext* cx) { 242 MOZ_ASSERT(obj_->getClass()->getNewEnumerate()); 243 244 RootedIdVector properties(cx); 245 bool enumerableOnly = !(flags_ & JSITER_HIDDEN); 246 if (!obj_->getClass()->getNewEnumerate()(cx, obj_, &properties, 247 enumerableOnly)) { 248 return false; 249 } 250 251 RootedId id(cx); 252 for (size_t n = 0; n < properties.length(); n++) { 253 id = properties[n]; 254 255 // The enumerate hook does not indicate whether the properties 256 // it returns are enumerable or not. Since we already passed 257 // `enumerableOnly` to the hook to filter out non-enumerable 258 // properties, it doesn't really matter what we pass here. 259 bool enumerable = true; 260 if (!enumerate<true>(cx, id, enumerable)) { 261 return false; 262 } 263 } 264 265 return true; 266 } 267 268 static bool SortComparatorIntegerIds(jsid a, jsid b, bool* lessOrEqualp) { 269 uint32_t indexA, indexB; 270 MOZ_ALWAYS_TRUE(IdIsIndex(a, &indexA)); 271 MOZ_ALWAYS_TRUE(IdIsIndex(b, &indexB)); 272 *lessOrEqualp = (indexA <= indexB); 273 return true; 274 } 275 276 template <bool CheckForDuplicates> 277 bool PropertyEnumerator::enumerateNativeProperties(JSContext* cx) { 278 Handle<NativeObject*> pobj = obj_.as<NativeObject>(); 279 280 if (Watchtower::watchesPropertyValueChange(pobj)) { 281 markIndicesUnsupported(); 282 } 283 284 // We don't need to iterate over the shape's properties if we're only 285 // interested in enumerable properties and the object is known to have no 286 // enumerable properties. 287 // 288 // Don't optimize if CheckForDuplicates is true, because non-enumerable 289 // properties still have to participate in duplicate-property checking. 290 const bool iterShapeProperties = CheckForDuplicates || 291 (flags_ & JSITER_HIDDEN) || 292 pobj->hasEnumerableProperty(); 293 294 bool enumerateSymbols; 295 if (flags_ & JSITER_SYMBOLSONLY) { 296 if (!iterShapeProperties) { 297 return true; 298 } 299 enumerateSymbols = true; 300 } else { 301 // Collect any dense elements from this object. 302 size_t firstElemIndex = props_.length(); 303 size_t initlen = pobj->getDenseInitializedLength(); 304 const Value* elements = pobj->getDenseElements(); 305 bool hasHoles = false; 306 for (uint32_t i = 0; i < initlen; ++i) { 307 if (elements[i].isMagic(JS_ELEMENTS_HOLE)) { 308 hasHoles = true; 309 } else { 310 // Dense arrays never get so large that i would not fit into an 311 // integer id. 312 if (!enumerate<CheckForDuplicates>(cx, PropertyKey::Int(i), 313 /* enumerable = */ true, 314 PropertyIndex::ForElement(i))) { 315 return false; 316 } 317 } 318 } 319 320 // Collect any typed array or shared typed array elements from this 321 // object. 322 if (pobj->is<TypedArrayObject>()) { 323 size_t len = pobj->as<TypedArrayObject>().length().valueOr(0); 324 325 // Fail early if the typed array is enormous, because this will be very 326 // slow and will likely report OOM. This also means we don't need to 327 // handle indices greater than PropertyKey::IntMax in the loop below. 328 static_assert(PropertyKey::IntMax == INT32_MAX); 329 if (len > INT32_MAX) { 330 ReportOutOfMemory(cx); 331 return false; 332 } 333 334 for (uint32_t i = 0; i < len; i++) { 335 if (!enumerate<CheckForDuplicates>(cx, PropertyKey::Int(i), 336 /* enumerable = */ true)) { 337 return false; 338 } 339 } 340 } 341 342 // The code below enumerates shape properties (including sparse elements) so 343 // if we can ignore those we're done. 344 if (!iterShapeProperties) { 345 return true; 346 } 347 348 // Collect any sparse elements from this object. 349 bool isIndexed = pobj->isIndexed(); 350 if (isIndexed) { 351 // If the dense elements didn't have holes, we don't need to include 352 // them in the sort. 353 if (!hasHoles) { 354 firstElemIndex = props_.length(); 355 } 356 357 for (ShapePropertyIter<NoGC> iter(pobj->shape()); !iter.done(); iter++) { 358 jsid id = iter->key(); 359 uint32_t dummy; 360 if (IdIsIndex(id, &dummy)) { 361 if (!enumerate<CheckForDuplicates>(cx, id, iter->enumerable())) { 362 return false; 363 } 364 } 365 } 366 367 MOZ_ASSERT(firstElemIndex <= props_.length()); 368 369 jsid* ids = props_.begin() + firstElemIndex; 370 size_t n = props_.length() - firstElemIndex; 371 372 RootedIdVector tmp(cx); 373 if (!tmp.resize(n)) { 374 return false; 375 } 376 PodCopy(tmp.begin(), ids, n); 377 378 if (!MergeSort(ids, n, tmp.begin(), SortComparatorIntegerIds)) { 379 return false; 380 } 381 } 382 383 size_t initialLength = props_.length(); 384 385 /* Collect all unique property names from this object's shape. */ 386 bool symbolsFound = false; 387 for (ShapePropertyIter<NoGC> iter(pobj->shape()); !iter.done(); iter++) { 388 jsid id = iter->key(); 389 390 if (id.isSymbol()) { 391 symbolsFound = true; 392 continue; 393 } 394 395 uint32_t dummy; 396 if (isIndexed && IdIsIndex(id, &dummy)) { 397 continue; 398 } 399 400 PropertyIndex index = iter->isDataProperty() && iter->writable() 401 ? PropertyIndex::ForSlot(pobj, iter->slot()) 402 : PropertyIndex::Invalid(); 403 if (!enumerate<CheckForDuplicates>(cx, id, iter->enumerable(), index)) { 404 return false; 405 } 406 } 407 reversePropsAndIndicesAfter(initialLength); 408 409 enumerateSymbols = symbolsFound && (flags_ & JSITER_SYMBOLS); 410 } 411 412 if (enumerateSymbols) { 413 MOZ_ASSERT(iterShapeProperties); 414 MOZ_ASSERT(!allocatingIndices()); 415 416 // Do a second pass to collect symbols. The spec requires that all symbols 417 // appear after all strings in [[OwnPropertyKeys]] for ordinary objects: 418 // https://tc39.es/ecma262/#sec-ordinaryownpropertykeys 419 size_t initialLength = props_.length(); 420 for (ShapePropertyIter<NoGC> iter(pobj->shape()); !iter.done(); iter++) { 421 jsid id = iter->key(); 422 if (id.isSymbol()) { 423 if (!enumerate<CheckForDuplicates>(cx, id, iter->enumerable())) { 424 return false; 425 } 426 } 427 } 428 reversePropsAndIndicesAfter(initialLength); 429 } 430 431 return true; 432 } 433 434 template <bool CheckForDuplicates> 435 bool PropertyEnumerator::enumerateProxyProperties(JSContext* cx) { 436 MOZ_ASSERT(obj_->is<ProxyObject>()); 437 438 RootedIdVector proxyProps(cx); 439 440 if (flags_ & JSITER_HIDDEN || flags_ & JSITER_SYMBOLS) { 441 // This gets all property keys, both strings and symbols. The call to 442 // enumerate in the loop below will filter out unwanted keys, per the 443 // flags. 444 if (!Proxy::ownPropertyKeys(cx, obj_, &proxyProps)) { 445 return false; 446 } 447 448 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx); 449 for (size_t n = 0, len = proxyProps.length(); n < len; n++) { 450 bool enumerable = false; 451 452 // We need to filter, if the caller just wants enumerable symbols. 453 if (!(flags_ & JSITER_HIDDEN)) { 454 if (!Proxy::getOwnPropertyDescriptor(cx, obj_, proxyProps[n], &desc)) { 455 return false; 456 } 457 enumerable = desc.isSome() && desc->enumerable(); 458 } 459 460 if (!enumerate<CheckForDuplicates>(cx, proxyProps[n], enumerable)) { 461 return false; 462 } 463 } 464 465 return true; 466 } 467 468 // Returns enumerable property names (no symbols). 469 if (!Proxy::getOwnEnumerablePropertyKeys(cx, obj_, &proxyProps)) { 470 return false; 471 } 472 473 for (size_t n = 0, len = proxyProps.length(); n < len; n++) { 474 if (!enumerate<CheckForDuplicates>(cx, proxyProps[n], true)) { 475 return false; 476 } 477 } 478 479 return true; 480 } 481 482 #ifdef DEBUG 483 484 struct SortComparatorIds { 485 JSContext* const cx; 486 487 explicit SortComparatorIds(JSContext* cx) : cx(cx) {} 488 489 bool operator()(jsid aArg, jsid bArg, bool* lessOrEqualp) { 490 RootedId a(cx, aArg); 491 RootedId b(cx, bArg); 492 493 // Pick an arbitrary order on jsids that is as stable as possible 494 // across executions. 495 if (a == b) { 496 *lessOrEqualp = true; 497 return true; 498 } 499 500 enum class KeyType { Void, Int, String, Symbol }; 501 502 auto keyType = [](PropertyKey key) { 503 if (key.isString()) { 504 return KeyType::String; 505 } 506 if (key.isInt()) { 507 return KeyType::Int; 508 } 509 if (key.isSymbol()) { 510 return KeyType::Symbol; 511 } 512 MOZ_ASSERT(key.isVoid()); 513 return KeyType::Void; 514 }; 515 516 if (keyType(a) != keyType(b)) { 517 *lessOrEqualp = (keyType(a) <= keyType(b)); 518 return true; 519 } 520 521 if (a.isInt()) { 522 *lessOrEqualp = (a.toInt() <= b.toInt()); 523 return true; 524 } 525 526 RootedString astr(cx), bstr(cx); 527 if (a.isSymbol()) { 528 MOZ_ASSERT(b.isSymbol()); 529 JS::SymbolCode ca = a.toSymbol()->code(); 530 JS::SymbolCode cb = b.toSymbol()->code(); 531 if (ca != cb) { 532 *lessOrEqualp = uint32_t(ca) <= uint32_t(cb); 533 return true; 534 } 535 MOZ_ASSERT(ca == JS::SymbolCode::PrivateNameSymbol || 536 ca == JS::SymbolCode::InSymbolRegistry || 537 ca == JS::SymbolCode::UniqueSymbol); 538 astr = a.toSymbol()->description(); 539 bstr = b.toSymbol()->description(); 540 if (!astr || !bstr) { 541 *lessOrEqualp = !astr; 542 return true; 543 } 544 545 // Fall through to string comparison on the descriptions. The sort 546 // order is nondeterministic if two different unique symbols have 547 // the same description. 548 } else { 549 astr = IdToString(cx, a); 550 if (!astr) { 551 return false; 552 } 553 bstr = IdToString(cx, b); 554 if (!bstr) { 555 return false; 556 } 557 } 558 559 int32_t result; 560 if (!CompareStrings(cx, astr, bstr, &result)) { 561 return false; 562 } 563 564 *lessOrEqualp = (result <= 0); 565 return true; 566 } 567 }; 568 569 #endif /* DEBUG */ 570 571 static void AssertNoEnumerableProperties(NativeObject* obj) { 572 #ifdef DEBUG 573 // Verify the object has no enumerable properties if the HasEnumerable 574 // ObjectFlag is not set. 575 576 MOZ_ASSERT(!obj->hasEnumerableProperty()); 577 578 static constexpr size_t MaxPropsToCheck = 5; 579 580 size_t count = 0; 581 for (ShapePropertyIter<NoGC> iter(obj->shape()); !iter.done(); iter++) { 582 MOZ_ASSERT(!iter->enumerable()); 583 if (++count > MaxPropsToCheck) { 584 break; 585 } 586 } 587 #endif // DEBUG 588 } 589 590 static bool ProtoMayHaveEnumerableProperties(JSObject* obj) { 591 if (!obj->is<NativeObject>()) { 592 return true; 593 } 594 595 JSObject* proto = obj->as<NativeObject>().staticPrototype(); 596 while (proto) { 597 if (!proto->is<NativeObject>()) { 598 return true; 599 } 600 NativeObject* nproto = &proto->as<NativeObject>(); 601 if (nproto->hasEnumerableProperty() || 602 nproto->getDenseInitializedLength() > 0 || 603 ClassCanHaveExtraEnumeratedProperties(nproto->getClass())) { 604 return true; 605 } 606 AssertNoEnumerableProperties(nproto); 607 proto = nproto->staticPrototype(); 608 } 609 610 return false; 611 } 612 613 bool PropertyEnumerator::snapshot(JSContext* cx) { 614 // If we're only interested in enumerable properties and the proto chain has 615 // no enumerable properties (the common case), we can optimize this to ignore 616 // the proto chain. This also lets us take advantage of the no-duplicate-check 617 // optimization below. 618 if (!(flags_ & JSITER_HIDDEN) && !(flags_ & JSITER_OWNONLY) && 619 !ProtoMayHaveEnumerableProperties(obj_)) { 620 flags_ |= JSITER_OWNONLY; 621 } 622 623 // Don't check for duplicates if we're only interested in own properties. 624 // This does the right thing for most objects: native objects don't have 625 // duplicate property ids and we allow the [[OwnPropertyKeys]] proxy trap to 626 // return duplicates. 627 // 628 // The only special case is when the object has a newEnumerate hook: it 629 // can return duplicate properties and we have to filter them. This is 630 // handled below. 631 bool checkForDuplicates = !(flags_ & JSITER_OWNONLY); 632 633 do { 634 if (obj_->getClass()->getNewEnumerate()) { 635 markIndicesUnsupported(); 636 637 if (!enumerateExtraProperties(cx)) { 638 return false; 639 } 640 641 if (obj_->is<NativeObject>()) { 642 if (!enumerateNativeProperties(cx, /*checkForDuplicates*/ true)) { 643 return false; 644 } 645 } 646 647 } else if (obj_->is<NativeObject>()) { 648 // Give the object a chance to resolve all lazy properties 649 if (JSEnumerateOp enumerateOp = obj_->getClass()->getEnumerate()) { 650 markIndicesUnsupported(); 651 if (!enumerateOp(cx, obj_.as<NativeObject>())) { 652 return false; 653 } 654 } 655 if (!enumerateNativeProperties(cx, checkForDuplicates)) { 656 return false; 657 } 658 } else if (obj_->is<ProxyObject>()) { 659 markIndicesUnsupported(); 660 if (checkForDuplicates) { 661 if (!enumerateProxyProperties<true>(cx)) { 662 return false; 663 } 664 } else { 665 if (!enumerateProxyProperties<false>(cx)) { 666 return false; 667 } 668 } 669 } else { 670 MOZ_CRASH("non-native objects must have an enumerate op"); 671 } 672 673 if (!enumeratingProtoChain_) { 674 ownPropertyCount_ = props_.length(); 675 } 676 677 if (flags_ & JSITER_OWNONLY) { 678 break; 679 } 680 681 if (!GetPrototype(cx, obj_, &obj_)) { 682 return false; 683 } 684 enumeratingProtoChain_ = true; 685 686 // The [[Prototype]] chain might be cyclic. 687 if (!CheckForInterrupt(cx)) { 688 return false; 689 } 690 } while (obj_ != nullptr); 691 692 #ifdef DEBUG 693 if (js::SupportDifferentialTesting() && !supportsIndices()) { 694 /* 695 * In some cases the enumeration order for an object depends on the 696 * execution mode (interpreter vs. JIT), especially for native objects 697 * with a class enumerate hook (where resolving a property changes the 698 * resulting enumeration order). These aren't really bugs, but the 699 * differences can change the generated output and confuse correctness 700 * fuzzers, so we sort the ids if such a fuzzer is running. 701 * 702 * We don't do this in the general case because (a) doing so is slow, 703 * and (b) it also breaks the web, which expects enumeration order to 704 * follow the order in which properties are added, in certain cases. 705 * Since ECMA does not specify an enumeration order for objects, both 706 * behaviors are technically correct to do. 707 */ 708 709 jsid* ids = props_.begin(); 710 size_t n = props_.length(); 711 712 RootedIdVector tmp(cx); 713 if (!tmp.resize(n)) { 714 return false; 715 } 716 PodCopy(tmp.begin(), ids, n); 717 718 if (!MergeSort(ids, n, tmp.begin(), SortComparatorIds(cx))) { 719 return false; 720 } 721 } 722 #endif 723 724 return true; 725 } 726 727 JS_PUBLIC_API bool js::GetPropertyKeys(JSContext* cx, HandleObject obj, 728 unsigned flags, 729 MutableHandleIdVector props) { 730 uint32_t validFlags = 731 flags & (JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | 732 JSITER_SYMBOLSONLY | JSITER_PRIVATE); 733 734 PropertyEnumerator enumerator(cx, obj, validFlags, props); 735 return enumerator.snapshot(cx); 736 } 737 738 static inline void RegisterEnumerator(JSContext* cx, NativeIterator* ni, 739 HandleObject obj) { 740 ni->initObjectBeingIterated(*obj); 741 742 // Register non-escaping native enumerators (for-in) with the current 743 // context. 744 ni->link(cx->compartment()->enumeratorsAddr()); 745 746 MOZ_ASSERT(!ni->isActive()); 747 ni->markActive(); 748 } 749 750 static PropertyIteratorObject* NewPropertyIteratorObject(JSContext* cx) { 751 const JSClass* clasp = &PropertyIteratorObject::class_; 752 Rooted<SharedShape*> shape( 753 cx, 754 SharedShape::getInitialShape(cx, clasp, cx->realm(), TaggedProto(nullptr), 755 ITERATOR_FINALIZE_KIND)); 756 if (!shape) { 757 return nullptr; 758 } 759 760 auto* res = NativeObject::create<PropertyIteratorObject>( 761 cx, ITERATOR_FINALIZE_KIND, GetInitialHeap(GenericObject, clasp), shape); 762 if (!res) { 763 return nullptr; 764 } 765 766 // CodeGenerator::visitIteratorStartO assumes the iterator object is not 767 // inside the nursery when deciding whether a barrier is necessary. 768 MOZ_ASSERT(!js::gc::IsInsideNursery(res)); 769 return res; 770 } 771 772 static inline size_t NumTrailingBytes(size_t propertyCount, 773 size_t protoShapeCount, bool hasIndices) { 774 static_assert(alignof(IteratorProperty) <= alignof(NativeIterator)); 775 static_assert(alignof(GCPtr<Shape*>) <= alignof(IteratorProperty)); 776 static_assert(alignof(PropertyIndex) <= alignof(GCPtr<Shape*>)); 777 size_t result = propertyCount * sizeof(IteratorProperty) + 778 protoShapeCount * sizeof(GCPtr<Shape*>); 779 if (hasIndices) { 780 result += propertyCount * sizeof(PropertyIndex); 781 } 782 return result; 783 } 784 785 static inline size_t AllocationSize(size_t propertyCount, 786 size_t protoShapeCount, bool hasIndices) { 787 return sizeof(NativeIterator) + 788 NumTrailingBytes(propertyCount, protoShapeCount, hasIndices); 789 } 790 791 static PropertyIteratorObject* CreatePropertyIterator( 792 JSContext* cx, Handle<JSObject*> objBeingIterated, HandleIdVector props, 793 bool supportsIndices, PropertyIndexVector* indices, 794 uint32_t cacheableProtoChainLength, uint32_t ownPropertyCount, 795 bool forObjectKeys) { 796 MOZ_ASSERT_IF(indices, supportsIndices); 797 if (props.length() >= NativeIterator::PropCountLimit) { 798 ReportAllocationOverflow(cx); 799 return nullptr; 800 } 801 802 bool hasIndices = !!indices; 803 804 // If the iterator is cacheable, we store the shape of each object 805 // along the proto chain in the iterator. If the iterator is not 806 // cacheable, but has indices, then we store one shape (the shape of 807 // the object being iterated.) 808 uint32_t numShapes = cacheableProtoChainLength; 809 if (numShapes == 0 && hasIndices) { 810 numShapes = 1; 811 } 812 if (numShapes > NativeIterator::ShapeCountLimit) { 813 ReportAllocationOverflow(cx); 814 return nullptr; 815 } 816 uint32_t numProtoShapes = numShapes > 0 ? numShapes - 1 : 0; 817 818 Rooted<PropertyIteratorObject*> propIter(cx, NewPropertyIteratorObject(cx)); 819 if (!propIter) { 820 return nullptr; 821 } 822 823 void* mem = cx->pod_malloc_with_extra<NativeIterator, uint8_t>( 824 NumTrailingBytes(props.length(), numProtoShapes, hasIndices)); 825 if (!mem) { 826 return nullptr; 827 } 828 829 // This also registers |ni| with |propIter|. 830 bool hadError = false; 831 new (mem) NativeIterator(cx, propIter, objBeingIterated, props, 832 supportsIndices, indices, numShapes, 833 ownPropertyCount, forObjectKeys, &hadError); 834 if (hadError) { 835 return nullptr; 836 } 837 838 return propIter; 839 } 840 841 static HashNumber HashIteratorShape(Shape* shape) { 842 return DefaultHasher<Shape*>::hash(shape); 843 } 844 845 /** 846 * Initialize a fresh NativeIterator. 847 * 848 * This definition is a bit tricky: some parts of initializing are fallible, so 849 * as we initialize, we must carefully keep this in GC-safe state (see 850 * NativeIterator::trace). 851 */ 852 NativeIterator::NativeIterator(JSContext* cx, 853 Handle<PropertyIteratorObject*> propIter, 854 Handle<JSObject*> objBeingIterated, 855 HandleIdVector props, bool supportsIndices, 856 PropertyIndexVector* indices, uint32_t numShapes, 857 uint32_t ownPropertyCount, bool forObjectKeys, 858 bool* hadError) 859 : objectBeingIterated_(nullptr), 860 iterObj_(propIter), 861 objShape_(numShapes > 0 ? objBeingIterated->shape() : nullptr), 862 // This holds the allocated property count until we're done with 863 // initialization 864 propertyCursor_(props.length()), 865 ownPropertyCount_(ownPropertyCount), 866 shapesHash_(0) { 867 // If there are shapes, the object and all objects on its prototype chain must 868 // be native objects. See CanCompareIterableObjectToCache. 869 MOZ_ASSERT_IF(numShapes > 0, 870 objBeingIterated && objBeingIterated->is<NativeObject>()); 871 872 MOZ_ASSERT(!*hadError); 873 874 bool hasActualIndices = !!indices; 875 MOZ_ASSERT_IF(hasActualIndices, indices->length() == props.length()); 876 877 if (hasActualIndices) { 878 flags_ |= Flags::IndicesAllocated; 879 } else if (supportsIndices) { 880 flags_ |= Flags::IndicesSupported; 881 } 882 883 if (forObjectKeys) { 884 flags_ |= Flags::OwnPropertiesOnly; 885 } 886 887 // NOTE: This must be done first thing: The caller can't free `this` on error 888 // because it has GCPtr fields whose barriers have already fired; the 889 // store buffer has pointers to them. Only the GC can free `this` (via 890 // PropertyIteratorObject::finalize). 891 propIter->initNativeIterator(this); 892 893 // The GC asserts on finalization that `this->allocationSize()` matches the 894 // `nbytes` passed to `AddCellMemory`. So once these lines run, we must make 895 // `this->allocationSize()` correct. That means infallibly initializing the 896 // shapes, and ensuring that indicesState_.allocated() is true if we've 897 // allocated space for indices. It's OK for the constructor to fail after 898 // that. 899 size_t nbytes = AllocationSize( 900 props.length(), numShapes > 0 ? numShapes - 1 : 0, hasActualIndices); 901 AddCellMemory(propIter, nbytes, MemoryUse::NativeIterator); 902 903 if (numShapes > 0) { 904 // Construct shapes into the shapes array. Also compute the shapesHash, 905 // which incorporates Shape* addresses that could have changed during a GC 906 // triggered in (among other places) |IdToString| above. 907 JSObject* pobj = objBeingIterated; 908 HashNumber shapesHash = 0; 909 for (uint32_t i = 0; i < numShapes; i++) { 910 MOZ_ASSERT(pobj->is<NativeObject>()); 911 Shape* shape = pobj->shape(); 912 if (i > 0) { 913 new (protoShapesEnd()) GCPtr<Shape*>(shape); 914 protoShapeCount_++; 915 } 916 shapesHash = mozilla::AddToHash(shapesHash, HashIteratorShape(shape)); 917 pobj = pobj->staticPrototype(); 918 } 919 shapesHash_ = shapesHash; 920 921 // There are two cases in which we need to store shapes. If this 922 // iterator is cacheable, we store the shapes for the entire proto 923 // chain so we can check that the cached iterator is still valid 924 // (see MacroAssembler::maybeLoadIteratorFromShape). If this iterator 925 // has indices, then even if it isn't cacheable we need to store the 926 // shape of the iterated object itself (see IteratorHasIndicesAndBranch). 927 // In the former case, assert that we're storing the entire proto chain. 928 MOZ_ASSERT_IF(numShapes > 1, pobj == nullptr); 929 MOZ_ASSERT(uintptr_t(protoShapesEnd()) == uintptr_t(this) + nbytes); 930 } 931 932 // Allocate any strings in the nursery until the first minor GC. After this 933 // point they will end up getting tenured anyway because they are reachable 934 // from |propIter| which will be tenured. 935 AutoSelectGCHeap gcHeap(cx); 936 937 bool maybeNeedGC = !gc::IsInsideNursery(propIter); 938 uint64_t gcNumber = cx->runtime()->gc.gcNumber(); 939 size_t numProps = props.length(); 940 for (size_t i = 0; i < numProps; i++) { 941 JSLinearString* str = IdToString(cx, props[i], gcHeap); 942 if (!str) { 943 *hadError = true; 944 return; 945 } 946 uint64_t newGcNumber = cx->runtime()->gc.gcNumber(); 947 if (newGcNumber != gcNumber) { 948 gcNumber = newGcNumber; 949 maybeNeedGC = true; 950 } 951 // We write to our IteratorProperty children only here and in 952 // PropertyIteratorObject::trace. Here we do not need a pre-barrier 953 // because we are not overwriting a previous value. 954 new (propertiesEnd()) IteratorProperty(str); 955 propertyCount_++; 956 if (maybeNeedGC && gc::IsInsideNursery(str)) { 957 maybeNeedGC = false; 958 cx->runtime()->gc.storeBuffer().putWholeCell(propIter); 959 } 960 } 961 962 if (hasActualIndices) { 963 PropertyIndex* cursor = indicesBegin(); 964 for (size_t i = 0; i < numProps; i++) { 965 *cursor++ = (*indices)[i]; 966 } 967 flags_ |= Flags::IndicesAvailable; 968 } 969 970 propertyCursor_ = 0; 971 flags_ |= Flags::Initialized; 972 973 MOZ_ASSERT(!*hadError); 974 } 975 976 inline size_t NativeIterator::allocationSize() const { 977 return AllocationSize(allocatedPropertyCount(), protoShapeCount_, 978 indicesAllocated()); 979 } 980 981 /* static */ 982 bool IteratorHashPolicy::match(PropertyIteratorObject* obj, 983 const Lookup& lookup) { 984 NativeIterator* ni = obj->getNativeIterator(); 985 if (ni->shapesHash() != lookup.shapesHash || 986 ni->protoShapeCount() != lookup.numProtoShapes || 987 ni->objShape() != lookup.objShape) { 988 return false; 989 } 990 991 return ArrayEqual(reinterpret_cast<Shape**>(ni->protoShapesBegin()), 992 lookup.protoShapes, ni->protoShapeCount()); 993 } 994 995 static inline bool CanCompareIterableObjectToCache(JSObject* obj) { 996 if (obj->is<NativeObject>()) { 997 return obj->as<NativeObject>().getDenseInitializedLength() == 0; 998 } 999 return false; 1000 } 1001 1002 static bool CanStoreInIteratorCache(JSObject* obj) { 1003 do { 1004 MOZ_ASSERT(obj->as<NativeObject>().getDenseInitializedLength() == 0); 1005 1006 // Typed arrays have indexed properties not captured by the Shape guard. 1007 // Enumerate hooks may add extra properties. 1008 if (MOZ_UNLIKELY(ClassCanHaveExtraEnumeratedProperties(obj->getClass()))) { 1009 return false; 1010 } 1011 1012 obj = obj->staticPrototype(); 1013 } while (obj); 1014 1015 return true; 1016 } 1017 1018 static MOZ_ALWAYS_INLINE PropertyIteratorObject* LookupInShapeIteratorCache( 1019 JSContext* cx, JSObject* obj, uint32_t* cacheableProtoChainLength, 1020 bool exclusive) { 1021 if (!obj->shape()->cache().isIterator() || 1022 !CanCompareIterableObjectToCache(obj)) { 1023 return nullptr; 1024 } 1025 PropertyIteratorObject* iterobj = obj->shape()->cache().toIterator(); 1026 NativeIterator* ni = iterobj->getNativeIterator(); 1027 MOZ_ASSERT(ni->objShape() == obj->shape()); 1028 if (exclusive && !ni->isReusable()) { 1029 return nullptr; 1030 } 1031 1032 // Verify shapes of proto chain. 1033 JSObject* pobj = obj; 1034 for (GCPtr<Shape*>* s = ni->protoShapesBegin(); s != ni->protoShapesEnd(); 1035 s++) { 1036 Shape* shape = *s; 1037 pobj = pobj->staticPrototype(); 1038 if (pobj->shape() != shape) { 1039 return nullptr; 1040 } 1041 if (!CanCompareIterableObjectToCache(pobj)) { 1042 return nullptr; 1043 } 1044 } 1045 MOZ_ASSERT(CanStoreInIteratorCache(obj)); 1046 *cacheableProtoChainLength = ni->objShape() ? ni->protoShapeCount() + 1 : 0; 1047 return iterobj; 1048 } 1049 1050 static MOZ_ALWAYS_INLINE PropertyIteratorObject* LookupInIteratorCache( 1051 JSContext* cx, JSObject* obj, uint32_t* cacheableProtoChainLength, 1052 bool exclusive) { 1053 MOZ_ASSERT(*cacheableProtoChainLength == 0); 1054 1055 if (PropertyIteratorObject* shapeCached = LookupInShapeIteratorCache( 1056 cx, obj, cacheableProtoChainLength, exclusive)) { 1057 return shapeCached; 1058 } 1059 1060 Vector<Shape*, 8> shapes(cx); 1061 HashNumber shapesHash = 0; 1062 JSObject* pobj = obj; 1063 do { 1064 if (!CanCompareIterableObjectToCache(pobj)) { 1065 return nullptr; 1066 } 1067 1068 MOZ_ASSERT(pobj->is<NativeObject>()); 1069 Shape* shape = pobj->shape(); 1070 shapesHash = mozilla::AddToHash(shapesHash, HashIteratorShape(shape)); 1071 1072 if (MOZ_UNLIKELY(!shapes.append(shape))) { 1073 cx->recoverFromOutOfMemory(); 1074 return nullptr; 1075 } 1076 1077 pobj = pobj->staticPrototype(); 1078 } while (pobj); 1079 1080 MOZ_ASSERT(!shapes.empty()); 1081 *cacheableProtoChainLength = shapes.length(); 1082 1083 IteratorHashPolicy::Lookup lookup(shapes[0], shapes.begin() + 1, 1084 shapes.length() - 1, shapesHash); 1085 auto p = ObjectRealm::get(obj).iteratorCache.lookup(lookup); 1086 if (!p) { 1087 return nullptr; 1088 } 1089 1090 PropertyIteratorObject* iterobj = *p; 1091 MOZ_ASSERT(iterobj->compartment() == cx->compartment()); 1092 1093 NativeIterator* ni = iterobj->getNativeIterator(); 1094 if (exclusive && !ni->isReusable()) { 1095 return nullptr; 1096 } 1097 1098 return iterobj; 1099 } 1100 1101 [[nodiscard]] static bool StoreInIteratorCache( 1102 JSContext* cx, JSObject* obj, PropertyIteratorObject* iterobj) { 1103 MOZ_ASSERT(CanStoreInIteratorCache(obj)); 1104 1105 NativeIterator* ni = iterobj->getNativeIterator(); 1106 MOZ_ASSERT(ni->objShape()); 1107 1108 obj->shape()->maybeCacheIterator(cx, iterobj); 1109 1110 IteratorHashPolicy::Lookup lookup( 1111 ni->objShape(), reinterpret_cast<Shape**>(ni->protoShapesBegin()), 1112 ni->protoShapeCount(), ni->shapesHash()); 1113 1114 ObjectRealm::IteratorCache& cache = ObjectRealm::get(obj).iteratorCache; 1115 bool ok; 1116 auto p = cache.lookupForAdd(lookup); 1117 if (MOZ_LIKELY(!p)) { 1118 ok = cache.add(p, iterobj); 1119 } else { 1120 // If we weren't able to use an existing cached iterator, just 1121 // replace it. 1122 cache.remove(p); 1123 ok = cache.relookupOrAdd(p, lookup, iterobj); 1124 } 1125 if (!ok) { 1126 ReportOutOfMemory(cx); 1127 return false; 1128 } 1129 1130 return true; 1131 } 1132 1133 bool js::EnumerateProperties(JSContext* cx, HandleObject obj, 1134 MutableHandleIdVector props) { 1135 MOZ_ASSERT(props.empty()); 1136 1137 if (MOZ_UNLIKELY(obj->is<ProxyObject>())) { 1138 return Proxy::enumerate(cx, obj, props); 1139 } 1140 1141 uint32_t flags = 0; 1142 PropertyEnumerator enumerator(cx, obj, flags, props); 1143 return enumerator.snapshot(cx); 1144 } 1145 1146 #ifdef DEBUG 1147 static bool IndicesAreValid(NativeObject* obj, NativeIterator* ni) { 1148 MOZ_ASSERT(ni->indicesAvailable()); 1149 size_t numDenseElements = obj->getDenseInitializedLength(); 1150 size_t numFixedSlots = obj->numFixedSlots(); 1151 const Value* elements = obj->getDenseElements(); 1152 1153 IteratorProperty* keys = ni->propertiesBegin(); 1154 PropertyIndex* indices = ni->indicesBegin(); 1155 1156 for (uint32_t i = 0; i < ni->numKeys(); i++) { 1157 PropertyIndex index = indices[i]; 1158 switch (index.kind()) { 1159 case PropertyIndex::Kind::Element: 1160 // Verify that the dense element exists and is not a hole. 1161 if (index.index() >= numDenseElements || 1162 elements[index.index()].isMagic(JS_ELEMENTS_HOLE)) { 1163 return false; 1164 } 1165 break; 1166 case PropertyIndex::Kind::FixedSlot: { 1167 // Verify that the slot exists and is an enumerable data property with 1168 // the expected key. 1169 Maybe<PropertyInfo> prop = 1170 obj->lookupPure(AtomToId(&keys[i].asString()->asAtom())); 1171 if (!prop.isSome() || !prop->hasSlot() || !prop->enumerable() || 1172 !prop->isDataProperty() || prop->slot() != index.index()) { 1173 return false; 1174 } 1175 break; 1176 } 1177 case PropertyIndex::Kind::DynamicSlot: { 1178 // Verify that the slot exists and is an enumerable data property with 1179 // the expected key. 1180 Maybe<PropertyInfo> prop = 1181 obj->lookupPure(AtomToId(&keys[i].asString()->asAtom())); 1182 if (!prop.isSome() || !prop->hasSlot() || !prop->enumerable() || 1183 !prop->isDataProperty() || 1184 prop->slot() - numFixedSlots != index.index()) { 1185 return false; 1186 } 1187 break; 1188 } 1189 case PropertyIndex::Kind::Invalid: 1190 return false; 1191 } 1192 } 1193 return true; 1194 } 1195 #endif 1196 1197 static PropertyIteratorObject* GetIteratorImpl(JSContext* cx, HandleObject obj, 1198 bool wantIndices, 1199 bool forObjectKeys) { 1200 MOZ_ASSERT(!obj->is<PropertyIteratorObject>()); 1201 MOZ_ASSERT(cx->compartment() == obj->compartment(), 1202 "We may end up allocating shapes in the wrong zone!"); 1203 1204 uint32_t cacheableProtoChainLength = 0; 1205 if (PropertyIteratorObject* iterobj = LookupInIteratorCache( 1206 cx, obj, &cacheableProtoChainLength, !forObjectKeys)) { 1207 NativeIterator* ni = iterobj->getNativeIterator(); 1208 bool recreateWithIndices = wantIndices && ni->indicesSupported(); 1209 bool recreateWithProtoProperties = 1210 !forObjectKeys && ni->ownPropertiesOnly(); 1211 if (!recreateWithIndices && !recreateWithProtoProperties) { 1212 MOZ_ASSERT_IF(wantIndices && ni->indicesAvailable(), 1213 IndicesAreValid(&obj->as<NativeObject>(), ni)); 1214 if (!forObjectKeys) { 1215 RegisterEnumerator(cx, ni, obj); 1216 } 1217 return iterobj; 1218 } 1219 // We don't want to get in a pattern where an Object.keys + indices 1220 // use case clobbers us because it wants indices, and then we clobber 1221 // it because we want prototype keys. This prevents that. 1222 if (!recreateWithIndices && ni->indicesAvailable()) { 1223 wantIndices = true; 1224 } 1225 } 1226 1227 if (cacheableProtoChainLength > 0 && !CanStoreInIteratorCache(obj)) { 1228 cacheableProtoChainLength = 0; 1229 } 1230 if (cacheableProtoChainLength > NativeIterator::ShapeCountLimit) { 1231 cacheableProtoChainLength = 0; 1232 } 1233 1234 RootedIdVector keys(cx); 1235 PropertyIndexVector indices(cx); 1236 bool supportsIndices = false; 1237 uint32_t ownPropertyCount = 0; 1238 1239 if (MOZ_UNLIKELY(obj->is<ProxyObject>())) { 1240 if (!Proxy::enumerate(cx, obj, &keys)) { 1241 return nullptr; 1242 } 1243 } else { 1244 uint32_t flags = 0; 1245 if (forObjectKeys) { 1246 flags |= JSITER_OWNONLY; 1247 } 1248 PropertyEnumerator enumerator(cx, obj, flags, &keys, &indices); 1249 if (!enumerator.snapshot(cx)) { 1250 return nullptr; 1251 } 1252 supportsIndices = enumerator.supportsIndices(); 1253 ownPropertyCount = enumerator.ownPropertyCount(); 1254 MOZ_ASSERT_IF(wantIndices && supportsIndices, 1255 keys.length() == indices.length()); 1256 } 1257 1258 // If the object has dense elements, mark the dense elements as 1259 // maybe-in-iteration. However if this is for Object.keys, we're not able to 1260 // do the appropriate invalidations on deletion etc. anyway. Accordingly, 1261 // we're forced to just disable the indices optimization for this iterator 1262 // entirely. 1263 // 1264 // The iterator is a snapshot so if indexed properties are added after this 1265 // point we don't need to do anything. However, the object might have sparse 1266 // elements now that can be densified later. To account for this, we set the 1267 // maybe-in-iteration flag also in NativeObject::maybeDensifySparseElements. 1268 // 1269 // In debug builds, AssertDenseElementsNotIterated is used to check the flag 1270 // is set correctly. 1271 if (obj->is<NativeObject>() && 1272 obj->as<NativeObject>().getDenseInitializedLength() > 0) { 1273 if (forObjectKeys) { 1274 supportsIndices = false; 1275 } else { 1276 obj->as<NativeObject>().markDenseElementsMaybeInIteration(); 1277 } 1278 } 1279 1280 PropertyIndexVector* indicesPtr = 1281 wantIndices && supportsIndices ? &indices : nullptr; 1282 PropertyIteratorObject* iterobj = CreatePropertyIterator( 1283 cx, obj, keys, supportsIndices, indicesPtr, cacheableProtoChainLength, 1284 ownPropertyCount, forObjectKeys); 1285 if (!iterobj) { 1286 return nullptr; 1287 } 1288 if (!forObjectKeys) { 1289 RegisterEnumerator(cx, iterobj->getNativeIterator(), obj); 1290 } 1291 1292 cx->check(iterobj); 1293 MOZ_ASSERT_IF( 1294 wantIndices && supportsIndices, 1295 IndicesAreValid(&obj->as<NativeObject>(), iterobj->getNativeIterator())); 1296 1297 #ifdef DEBUG 1298 if (obj->is<NativeObject>()) { 1299 if (PrototypeMayHaveIndexedProperties(&obj->as<NativeObject>())) { 1300 iterobj->getNativeIterator()->setMaybeHasIndexedPropertiesFromProto(); 1301 } 1302 } 1303 #endif 1304 1305 // Cache the iterator object. 1306 if (cacheableProtoChainLength > 0) { 1307 if (!StoreInIteratorCache(cx, obj, iterobj)) { 1308 return nullptr; 1309 } 1310 } 1311 1312 return iterobj; 1313 } 1314 1315 PropertyIteratorObject* js::GetIterator(JSContext* cx, HandleObject obj) { 1316 return GetIteratorImpl(cx, obj, false, false); 1317 } 1318 1319 PropertyIteratorObject* js::GetIteratorWithIndices(JSContext* cx, 1320 HandleObject obj) { 1321 return GetIteratorImpl(cx, obj, true, false); 1322 } 1323 1324 PropertyIteratorObject* js::GetIteratorForObjectKeys(JSContext* cx, 1325 HandleObject obj) { 1326 return GetIteratorImpl(cx, obj, false, true); 1327 } 1328 1329 PropertyIteratorObject* js::GetIteratorWithIndicesForObjectKeys( 1330 JSContext* cx, HandleObject obj) { 1331 return GetIteratorImpl(cx, obj, true, true); 1332 } 1333 1334 PropertyIteratorObject* js::LookupInIteratorCache(JSContext* cx, 1335 HandleObject obj) { 1336 uint32_t dummy = 0; 1337 return LookupInIteratorCache(cx, obj, &dummy, true); 1338 } 1339 1340 PropertyIteratorObject* js::LookupInShapeIteratorCache(JSContext* cx, 1341 HandleObject obj) { 1342 uint32_t dummy = 0; 1343 return LookupInShapeIteratorCache(cx, obj, &dummy, true); 1344 } 1345 1346 // ES 2017 draft 7.4.7. 1347 PlainObject* js::CreateIterResultObject(JSContext* cx, HandleValue value, 1348 bool done) { 1349 // Step 1 (implicit). 1350 1351 // Step 2. 1352 Rooted<PlainObject*> templateObject( 1353 cx, GlobalObject::getOrCreateIterResultTemplateObject(cx)); 1354 if (!templateObject) { 1355 return nullptr; 1356 } 1357 1358 PlainObject* resultObj = PlainObject::createWithTemplate(cx, templateObject); 1359 if (!resultObj) { 1360 return nullptr; 1361 } 1362 1363 // Step 3. 1364 resultObj->setSlot(GlobalObject::IterResultObjectValueSlot, value); 1365 1366 // Step 4. 1367 resultObj->setSlot(GlobalObject::IterResultObjectDoneSlot, 1368 done ? TrueHandleValue : FalseHandleValue); 1369 1370 // Step 5. 1371 return resultObj; 1372 } 1373 1374 PlainObject* GlobalObject::getOrCreateIterResultTemplateObject(JSContext* cx) { 1375 GCPtr<PlainObject*>& obj = cx->global()->data().iterResultTemplate; 1376 if (obj) { 1377 return obj; 1378 } 1379 1380 PlainObject* templateObj = 1381 createIterResultTemplateObject(cx, WithObjectPrototype::Yes); 1382 obj.init(templateObj); 1383 return obj; 1384 } 1385 1386 /* static */ 1387 PlainObject* GlobalObject::getOrCreateIterResultWithoutPrototypeTemplateObject( 1388 JSContext* cx) { 1389 GCPtr<PlainObject*>& obj = 1390 cx->global()->data().iterResultWithoutPrototypeTemplate; 1391 if (obj) { 1392 return obj; 1393 } 1394 1395 PlainObject* templateObj = 1396 createIterResultTemplateObject(cx, WithObjectPrototype::No); 1397 obj.init(templateObj); 1398 return obj; 1399 } 1400 1401 /* static */ 1402 PlainObject* GlobalObject::createIterResultTemplateObject( 1403 JSContext* cx, WithObjectPrototype withProto) { 1404 // Create template plain object 1405 Rooted<PlainObject*> templateObject( 1406 cx, withProto == WithObjectPrototype::Yes 1407 ? NewPlainObject(cx, TenuredObject) 1408 : NewPlainObjectWithProto(cx, nullptr)); 1409 if (!templateObject) { 1410 return nullptr; 1411 } 1412 1413 // Set dummy `value` property 1414 if (!NativeDefineDataProperty(cx, templateObject, cx->names().value, 1415 UndefinedHandleValue, JSPROP_ENUMERATE)) { 1416 return nullptr; 1417 } 1418 1419 // Set dummy `done` property 1420 if (!NativeDefineDataProperty(cx, templateObject, cx->names().done, 1421 TrueHandleValue, JSPROP_ENUMERATE)) { 1422 return nullptr; 1423 } 1424 1425 #ifdef DEBUG 1426 // Make sure that the properties are in the right slots. 1427 ShapePropertyIter<NoGC> iter(templateObject->shape()); 1428 MOZ_ASSERT(iter->slot() == GlobalObject::IterResultObjectDoneSlot && 1429 iter->key() == NameToId(cx->names().done)); 1430 iter++; 1431 MOZ_ASSERT(iter->slot() == GlobalObject::IterResultObjectValueSlot && 1432 iter->key() == NameToId(cx->names().value)); 1433 #endif 1434 1435 return templateObject; 1436 } 1437 1438 /*** Iterator objects *******************************************************/ 1439 1440 size_t PropertyIteratorObject::sizeOfMisc( 1441 mozilla::MallocSizeOf mallocSizeOf) const { 1442 return mallocSizeOf(getNativeIterator()); 1443 } 1444 1445 void PropertyIteratorObject::trace(JSTracer* trc, JSObject* obj) { 1446 if (NativeIterator* ni = 1447 obj->as<PropertyIteratorObject>().getNativeIterator()) { 1448 ni->trace(trc); 1449 } 1450 } 1451 1452 void PropertyIteratorObject::finalize(JS::GCContext* gcx, JSObject* obj) { 1453 if (NativeIterator* ni = 1454 obj->as<PropertyIteratorObject>().getNativeIterator()) { 1455 gcx->free_(obj, ni, ni->allocationSize(), MemoryUse::NativeIterator); 1456 } 1457 } 1458 1459 const JSClassOps PropertyIteratorObject::classOps_ = { 1460 nullptr, // addProperty 1461 nullptr, // delProperty 1462 nullptr, // enumerate 1463 nullptr, // newEnumerate 1464 nullptr, // resolve 1465 nullptr, // mayResolve 1466 finalize, // finalize 1467 nullptr, // call 1468 nullptr, // construct 1469 trace, // trace 1470 }; 1471 1472 const JSClass PropertyIteratorObject::class_ = { 1473 "Iterator", 1474 JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_BACKGROUND_FINALIZE, 1475 &PropertyIteratorObject::classOps_, 1476 }; 1477 1478 static const JSClass ArrayIteratorPrototypeClass = { 1479 "Array Iterator", 1480 0, 1481 }; 1482 1483 enum { 1484 ArrayIteratorSlotIteratedObject, 1485 ArrayIteratorSlotNextIndex, 1486 ArrayIteratorSlotItemKind, 1487 ArrayIteratorSlotCount 1488 }; 1489 // Slot numbers must match constants used in self-hosted code. 1490 static_assert(ArrayIteratorSlotIteratedObject == ITERATOR_SLOT_TARGET); 1491 static_assert(ArrayIteratorSlotNextIndex == ITERATOR_SLOT_NEXT_INDEX); 1492 static_assert(ArrayIteratorSlotItemKind == ARRAY_ITERATOR_SLOT_ITEM_KIND); 1493 1494 const JSClass ArrayIteratorObject::class_ = { 1495 "Array Iterator", 1496 JSCLASS_HAS_RESERVED_SLOTS(ArrayIteratorSlotCount), 1497 }; 1498 1499 ArrayIteratorObject* js::NewArrayIteratorTemplate(JSContext* cx) { 1500 RootedObject proto( 1501 cx, GlobalObject::getOrCreateArrayIteratorPrototype(cx, cx->global())); 1502 if (!proto) { 1503 return nullptr; 1504 } 1505 1506 return NewTenuredObjectWithGivenProto<ArrayIteratorObject>(cx, proto); 1507 } 1508 1509 ArrayIteratorObject* js::NewArrayIterator(JSContext* cx) { 1510 RootedObject proto( 1511 cx, GlobalObject::getOrCreateArrayIteratorPrototype(cx, cx->global())); 1512 if (!proto) { 1513 return nullptr; 1514 } 1515 1516 return NewObjectWithGivenProto<ArrayIteratorObject>(cx, proto); 1517 } 1518 1519 static const JSFunctionSpec array_iterator_methods[] = { 1520 JS_SELF_HOSTED_FN("next", "ArrayIteratorNext", 0, 0), 1521 JS_FS_END, 1522 }; 1523 1524 static const JSClass StringIteratorPrototypeClass = { 1525 "String Iterator", 1526 0, 1527 }; 1528 1529 enum { 1530 StringIteratorSlotIteratedObject, 1531 StringIteratorSlotNextIndex, 1532 StringIteratorSlotCount 1533 }; 1534 // Slot numbers must match constants used in self-hosted code. 1535 static_assert(StringIteratorSlotIteratedObject == ITERATOR_SLOT_TARGET); 1536 static_assert(StringIteratorSlotNextIndex == ITERATOR_SLOT_NEXT_INDEX); 1537 1538 const JSClass StringIteratorObject::class_ = { 1539 "String Iterator", 1540 JSCLASS_HAS_RESERVED_SLOTS(StringIteratorSlotCount), 1541 }; 1542 1543 static const JSFunctionSpec string_iterator_methods[] = { 1544 JS_SELF_HOSTED_FN("next", "StringIteratorNext", 0, 0), 1545 JS_FS_END, 1546 }; 1547 1548 StringIteratorObject* js::NewStringIteratorTemplate(JSContext* cx) { 1549 RootedObject proto( 1550 cx, GlobalObject::getOrCreateStringIteratorPrototype(cx, cx->global())); 1551 if (!proto) { 1552 return nullptr; 1553 } 1554 1555 return NewTenuredObjectWithGivenProto<StringIteratorObject>(cx, proto); 1556 } 1557 1558 StringIteratorObject* js::NewStringIterator(JSContext* cx) { 1559 RootedObject proto( 1560 cx, GlobalObject::getOrCreateStringIteratorPrototype(cx, cx->global())); 1561 if (!proto) { 1562 return nullptr; 1563 } 1564 1565 return NewObjectWithGivenProto<StringIteratorObject>(cx, proto); 1566 } 1567 1568 static const JSClass RegExpStringIteratorPrototypeClass = { 1569 "RegExp String Iterator", 1570 0, 1571 }; 1572 1573 enum { 1574 // The regular expression used for iteration. May hold the original RegExp 1575 // object when it is reused instead of a new RegExp object. 1576 RegExpStringIteratorSlotRegExp, 1577 1578 // The String value being iterated upon. 1579 RegExpStringIteratorSlotString, 1580 1581 // The source string of the original RegExp object. Used to validate we can 1582 // reuse the original RegExp object for matching. 1583 RegExpStringIteratorSlotSource, 1584 1585 // The flags of the original RegExp object. 1586 RegExpStringIteratorSlotFlags, 1587 1588 // When non-negative, this slot holds the current lastIndex position when 1589 // reusing the original RegExp object for matching. When set to |-1|, the 1590 // iterator has finished. When set to any other negative value, the 1591 // iterator is not yet exhausted and we're not on the fast path and we're 1592 // not reusing the input RegExp object. 1593 RegExpStringIteratorSlotLastIndex, 1594 1595 RegExpStringIteratorSlotCount 1596 }; 1597 1598 static_assert(RegExpStringIteratorSlotRegExp == 1599 REGEXP_STRING_ITERATOR_REGEXP_SLOT, 1600 "RegExpStringIteratorSlotRegExp must match self-hosting define " 1601 "for regexp slot."); 1602 static_assert(RegExpStringIteratorSlotString == 1603 REGEXP_STRING_ITERATOR_STRING_SLOT, 1604 "RegExpStringIteratorSlotString must match self-hosting define " 1605 "for string slot."); 1606 static_assert(RegExpStringIteratorSlotSource == 1607 REGEXP_STRING_ITERATOR_SOURCE_SLOT, 1608 "RegExpStringIteratorSlotString must match self-hosting define " 1609 "for source slot."); 1610 static_assert(RegExpStringIteratorSlotFlags == 1611 REGEXP_STRING_ITERATOR_FLAGS_SLOT, 1612 "RegExpStringIteratorSlotFlags must match self-hosting define " 1613 "for flags slot."); 1614 static_assert(RegExpStringIteratorSlotLastIndex == 1615 REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, 1616 "RegExpStringIteratorSlotLastIndex must match self-hosting " 1617 "define for lastIndex slot."); 1618 1619 const JSClass RegExpStringIteratorObject::class_ = { 1620 "RegExp String Iterator", 1621 JSCLASS_HAS_RESERVED_SLOTS(RegExpStringIteratorSlotCount), 1622 }; 1623 1624 static const JSFunctionSpec regexp_string_iterator_methods[] = { 1625 JS_SELF_HOSTED_FN("next", "RegExpStringIteratorNext", 0, 0), 1626 1627 JS_FS_END, 1628 }; 1629 1630 RegExpStringIteratorObject* js::NewRegExpStringIteratorTemplate(JSContext* cx) { 1631 RootedObject proto(cx, GlobalObject::getOrCreateRegExpStringIteratorPrototype( 1632 cx, cx->global())); 1633 if (!proto) { 1634 return nullptr; 1635 } 1636 1637 return NewTenuredObjectWithGivenProto<RegExpStringIteratorObject>(cx, proto); 1638 } 1639 1640 RegExpStringIteratorObject* js::NewRegExpStringIterator(JSContext* cx) { 1641 RootedObject proto(cx, GlobalObject::getOrCreateRegExpStringIteratorPrototype( 1642 cx, cx->global())); 1643 if (!proto) { 1644 return nullptr; 1645 } 1646 1647 return NewObjectWithGivenProto<RegExpStringIteratorObject>(cx, proto); 1648 } 1649 1650 #ifdef NIGHTLY_BUILD 1651 static const JSClass IteratorRangePrototypeClass = { 1652 "Numeric Range Iterator", 1653 0, 1654 }; 1655 1656 enum { 1657 IteratorRangeSlotStart, 1658 IteratorRangeSlotEnd, 1659 IteratorRangeSlotStep, 1660 IteratorRangeSlotInclusiveEnd, 1661 IteratorRangeSlotZero, 1662 IteratorRangeSlotOne, 1663 IteratorRangeSlotCurrentCount, 1664 IteratorRangeSlotCount 1665 }; 1666 1667 // slot numbers must match constants used in self-hosted code 1668 static_assert(IteratorRangeSlotStart == ITERATOR_RANGE_SLOT_START); 1669 static_assert(IteratorRangeSlotEnd == ITERATOR_RANGE_SLOT_END); 1670 static_assert(IteratorRangeSlotStep == ITERATOR_RANGE_SLOT_STEP); 1671 static_assert(IteratorRangeSlotInclusiveEnd == 1672 ITERATOR_RANGE_SLOT_INCLUSIVE_END); 1673 static_assert(IteratorRangeSlotZero == ITERATOR_RANGE_SLOT_ZERO); 1674 static_assert(IteratorRangeSlotOne == ITERATOR_RANGE_SLOT_ONE); 1675 static_assert(IteratorRangeSlotCurrentCount == 1676 ITERATOR_RANGE_SLOT_CURRENT_COUNT); 1677 1678 static const JSFunctionSpec iterator_range_methods[] = { 1679 JS_SELF_HOSTED_FN("next", "IteratorRangeNext", 0, 0), 1680 JS_FS_END, 1681 }; 1682 1683 IteratorRangeObject* js::NewIteratorRange(JSContext* cx) { 1684 RootedObject proto( 1685 cx, GlobalObject::getOrCreateIteratorRangePrototype(cx, cx->global())); 1686 if (!proto) { 1687 return nullptr; 1688 } 1689 1690 return NewObjectWithGivenProto<IteratorRangeObject>(cx, proto); 1691 } 1692 #endif 1693 1694 // static 1695 PropertyIteratorObject* GlobalObject::getOrCreateEmptyIterator(JSContext* cx) { 1696 if (!cx->global()->data().emptyIterator) { 1697 RootedIdVector props(cx); // Empty 1698 PropertyIteratorObject* iter = 1699 CreatePropertyIterator(cx, nullptr, props, false, nullptr, 0, 0, false); 1700 if (!iter) { 1701 return nullptr; 1702 } 1703 iter->getNativeIterator()->markEmptyIteratorSingleton(); 1704 cx->global()->data().emptyIterator.init(iter); 1705 } 1706 return cx->global()->data().emptyIterator; 1707 } 1708 1709 PropertyIteratorObject* js::ValueToIterator(JSContext* cx, HandleValue vp) { 1710 RootedObject obj(cx); 1711 if (vp.isObject()) { 1712 /* Common case. */ 1713 obj = &vp.toObject(); 1714 } else if (vp.isNullOrUndefined()) { 1715 /* 1716 * Enumerating over null and undefined gives an empty enumerator, so 1717 * that |for (var p in <null or undefined>) <loop>;| never executes 1718 * <loop>, per ES5 12.6.4. 1719 */ 1720 return GlobalObject::getOrCreateEmptyIterator(cx); 1721 } else { 1722 obj = ToObject(cx, vp); 1723 if (!obj) { 1724 return nullptr; 1725 } 1726 } 1727 1728 return GetIterator(cx, obj); 1729 } 1730 1731 void js::CloseIterator(JSObject* obj) { 1732 if (!obj->is<PropertyIteratorObject>()) { 1733 return; 1734 } 1735 1736 // Remove iterator from the active list, which is a stack. The shared iterator 1737 // used for for-in with null/undefined is immutable and unlinked. 1738 1739 NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator(); 1740 if (ni->isEmptyIteratorSingleton()) { 1741 return; 1742 } 1743 1744 ni->unlink(); 1745 1746 MOZ_ASSERT(ni->isActive()); 1747 ni->markInactive(); 1748 1749 ni->clearObjectBeingIterated(); 1750 1751 // Reset the enumerator; it may still be in the cached iterators for 1752 // this thread and can be reused. 1753 ni->resetPropertyCursorForReuse(); 1754 } 1755 1756 bool js::IteratorCloseForException(JSContext* cx, HandleObject obj) { 1757 MOZ_ASSERT(cx->isExceptionPending()); 1758 1759 // Closing an iterator is implemented as an exception. 1760 bool isClosingGenerator = cx->isClosingGenerator(); 1761 1762 // Save the current exception state. This implicitly clears any pending 1763 // exception, so it needs to happen after calling |cx->isClosingGenerator()|. 1764 // The destructor restores the saved exception state, unless there's a new 1765 // pending exception. 1766 JS::AutoSaveExceptionState savedExc(cx); 1767 1768 // CloseIterOperation when called with |CompletionKind::Throw| clears any 1769 // pending exception, so the previously stored exception in |savedExc| is 1770 // correctly restored. 1771 // When called with |CompletionKind::Return|, pending exceptions aren't 1772 // cleared, so the "generator closing" exception state in |savedExc| is only 1773 // restored if there isn't a new pending exception. 1774 auto completionKind = 1775 isClosingGenerator ? CompletionKind::Return : CompletionKind::Throw; 1776 return CloseIterOperation(cx, obj, completionKind); 1777 } 1778 1779 void js::UnwindIteratorForUncatchableException(JSObject* obj) { 1780 if (obj->is<PropertyIteratorObject>()) { 1781 NativeIterator* ni = obj->as<PropertyIteratorObject>().getNativeIterator(); 1782 if (ni->isEmptyIteratorSingleton()) { 1783 return; 1784 } 1785 ni->unlink(); 1786 } 1787 } 1788 1789 static bool SuppressDeletedProperty(JSContext* cx, NativeIterator* ni, 1790 HandleObject obj, 1791 Handle<JSLinearString*> str) { 1792 if (ni->objectBeingIterated() != obj) { 1793 return true; 1794 } 1795 1796 ni->disableIndices(); 1797 1798 // Optimization for the following common case: 1799 // 1800 // for (var p in o) { 1801 // delete o[p]; 1802 // } 1803 // 1804 // Note that usually both strings will be atoms so we only check for pointer 1805 // equality here. 1806 if (ni->previousPropertyWas(str)) { 1807 return true; 1808 } 1809 1810 // Check whether id is still to come. 1811 Rooted<JSLinearString*> idStr(cx); 1812 IteratorProperty* cursor = ni->nextProperty(); 1813 for (; cursor < ni->propertiesEnd(); ++cursor) { 1814 idStr = cursor->asString(); 1815 // Common case: both strings are atoms. 1816 if (idStr->isAtom() && str->isAtom()) { 1817 if (idStr != str) { 1818 continue; 1819 } 1820 } else { 1821 if (!EqualStrings(idStr, str)) { 1822 continue; 1823 } 1824 } 1825 1826 // Check whether another property along the prototype chain became 1827 // visible as a result of this deletion. 1828 RootedObject proto(cx); 1829 if (!GetPrototype(cx, obj, &proto)) { 1830 return false; 1831 } 1832 if (proto) { 1833 RootedId id(cx); 1834 RootedValue idv(cx, StringValue(idStr)); 1835 if (!PrimitiveValueToId<CanGC>(cx, idv, &id)) { 1836 return false; 1837 } 1838 1839 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx); 1840 RootedObject holder(cx); 1841 if (!GetPropertyDescriptor(cx, proto, id, &desc, &holder)) { 1842 return false; 1843 } 1844 1845 // If deletion just made something up the chain visible, no need to 1846 // do anything. 1847 if (desc.isSome() && desc->enumerable()) { 1848 return true; 1849 } 1850 } 1851 1852 cursor->markDeleted(); 1853 ni->markHasUnvisitedPropertyDeletion(); 1854 return true; 1855 } 1856 1857 return true; 1858 } 1859 1860 /* 1861 * Suppress enumeration of deleted properties. This function must be called 1862 * when a property is deleted and there might be active enumerators. 1863 * 1864 * We maintain a list of active non-escaping for-in enumerators. To suppress 1865 * a property, we check whether each active enumerator contains the (obj, id) 1866 * pair and has not yet enumerated |id|. If so, and |id| is the next property, 1867 * we simply advance the cursor. Otherwise, we delete |id| from the list. 1868 * 1869 * We do not suppress enumeration of a property deleted along an object's 1870 * prototype chain. Only direct deletions on the object are handled. 1871 */ 1872 static bool SuppressDeletedPropertyHelper(JSContext* cx, HandleObject obj, 1873 Handle<JSLinearString*> str) { 1874 NativeIteratorListIter iter(obj->compartment()->enumeratorsAddr()); 1875 while (!iter.done()) { 1876 NativeIterator* ni = iter.next(); 1877 if (!SuppressDeletedProperty(cx, ni, obj, str)) { 1878 return false; 1879 } 1880 } 1881 1882 return true; 1883 } 1884 1885 bool js::SuppressDeletedProperty(JSContext* cx, HandleObject obj, jsid id) { 1886 if (MOZ_LIKELY(!obj->compartment()->objectMaybeInIteration(obj))) { 1887 return true; 1888 } 1889 1890 if (id.isSymbol()) { 1891 return true; 1892 } 1893 1894 Rooted<JSLinearString*> str(cx, IdToString(cx, id)); 1895 if (!str) { 1896 return false; 1897 } 1898 return SuppressDeletedPropertyHelper(cx, obj, str); 1899 } 1900 1901 bool js::SuppressDeletedElement(JSContext* cx, HandleObject obj, 1902 uint32_t index) { 1903 if (MOZ_LIKELY(!obj->compartment()->objectMaybeInIteration(obj))) { 1904 return true; 1905 } 1906 1907 RootedId id(cx); 1908 if (!IndexToId(cx, index, &id)) { 1909 return false; 1910 } 1911 1912 Rooted<JSLinearString*> str(cx, IdToString(cx, id)); 1913 if (!str) { 1914 return false; 1915 } 1916 return SuppressDeletedPropertyHelper(cx, obj, str); 1917 } 1918 1919 #ifdef DEBUG 1920 void js::AssertDenseElementsNotIterated(NativeObject* obj) { 1921 // Search for active iterators for |obj| and assert they don't contain any 1922 // property keys that are dense elements. This is used to check correctness 1923 // of the MAYBE_IN_ITERATION flag on ObjectElements. 1924 // 1925 // Ignore iterators that may contain indexed properties from objects on the 1926 // prototype chain, as that can result in false positives. See bug 1656744. 1927 1928 // Limit the number of properties we check to avoid slowing down debug builds 1929 // too much. 1930 static constexpr uint32_t MaxPropsToCheck = 10; 1931 uint32_t propsChecked = 0; 1932 1933 NativeIteratorListIter iter(obj->compartment()->enumeratorsAddr()); 1934 while (!iter.done()) { 1935 NativeIterator* ni = iter.next(); 1936 if (ni->objectBeingIterated() == obj && 1937 !ni->maybeHasIndexedPropertiesFromProto()) { 1938 for (IteratorProperty* idp = ni->nextProperty(); 1939 idp < ni->propertiesEnd(); ++idp) { 1940 uint32_t index; 1941 if (idp->asString()->isIndex(&index)) { 1942 MOZ_ASSERT(!obj->containsDenseElement(index)); 1943 } 1944 if (++propsChecked > MaxPropsToCheck) { 1945 return; 1946 } 1947 } 1948 } 1949 } 1950 } 1951 #endif 1952 1953 static const JSFunctionSpec iterator_static_methods[] = { 1954 JS_SELF_HOSTED_FN("from", "IteratorFrom", 1, 0), 1955 JS_SELF_HOSTED_FN("concat", "IteratorConcat", 0, 0), 1956 #ifdef NIGHTLY_BUILD 1957 JS_SELF_HOSTED_FN("range", "IteratorRange", 3, 0), 1958 #endif 1959 JS_SELF_HOSTED_FN("zip", "IteratorZip", 2, 0), 1960 JS_SELF_HOSTED_FN("zipKeyed", "IteratorZipKeyed", 2, 0), 1961 JS_FS_END, 1962 }; 1963 1964 // These methods are only attached to Iterator.prototype when the 1965 // Iterator Helpers feature is enabled. 1966 static const JSFunctionSpec iterator_methods[] = { 1967 JS_SELF_HOSTED_FN("map", "IteratorMap", 1, 0), 1968 JS_SELF_HOSTED_FN("filter", "IteratorFilter", 1, 0), 1969 JS_SELF_HOSTED_FN("take", "IteratorTake", 1, 0), 1970 JS_SELF_HOSTED_FN("drop", "IteratorDrop", 1, 0), 1971 JS_SELF_HOSTED_FN("flatMap", "IteratorFlatMap", 1, 0), 1972 JS_SELF_HOSTED_FN("reduce", "IteratorReduce", 1, 0), 1973 JS_SELF_HOSTED_FN("toArray", "IteratorToArray", 0, 0), 1974 JS_SELF_HOSTED_FN("forEach", "IteratorForEach", 1, 0), 1975 JS_SELF_HOSTED_FN("some", "IteratorSome", 1, 0), 1976 JS_SELF_HOSTED_FN("every", "IteratorEvery", 1, 0), 1977 JS_SELF_HOSTED_FN("find", "IteratorFind", 1, 0), 1978 JS_SELF_HOSTED_SYM_FN(iterator, "IteratorIdentity", 0, 0), 1979 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 1980 JS_SELF_HOSTED_SYM_FN(dispose, "IteratorDispose", 0, 0), 1981 #endif 1982 #ifdef NIGHTLY_BUILD 1983 JS_SELF_HOSTED_FN("chunks", "IteratorChunks", 1, 0), 1984 JS_SELF_HOSTED_FN("windows", "IteratorWindows", 2, 0), 1985 JS_SELF_HOSTED_FN("join", "IteratorJoin", 1, 0), 1986 #endif 1987 JS_FS_END, 1988 }; 1989 1990 // https://tc39.es/proposal-iterator-helpers/#sec-SetterThatIgnoresPrototypeProperties 1991 static bool SetterThatIgnoresPrototypeProperties(JSContext* cx, 1992 Handle<Value> thisv, 1993 Handle<PropertyKey> prop, 1994 Handle<Value> value) { 1995 // Step 1. 1996 Rooted<JSObject*> thisObj(cx, 1997 RequireObject(cx, JSMSG_OBJECT_REQUIRED, thisv)); 1998 if (!thisObj) { 1999 return false; 2000 } 2001 2002 // Step 2. 2003 JSObject* home = GlobalObject::getOrCreateIteratorPrototype(cx, cx->global()); 2004 if (!home) { 2005 return false; 2006 } 2007 if (thisObj == home) { 2008 UniqueChars propName = 2009 IdToPrintableUTF8(cx, prop, IdToPrintableBehavior::IdIsPropertyKey); 2010 if (!propName) { 2011 return false; 2012 } 2013 2014 // Step 2.b. 2015 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READ_ONLY, 2016 propName.get()); 2017 return false; 2018 } 2019 2020 // Step 3. 2021 Rooted<Maybe<PropertyDescriptor>> desc(cx); 2022 if (!GetOwnPropertyDescriptor(cx, thisObj, prop, &desc)) { 2023 return false; 2024 } 2025 2026 // Step 4. 2027 if (desc.isNothing()) { 2028 // Step 4.a. 2029 return DefineDataProperty(cx, thisObj, prop, value, JSPROP_ENUMERATE); 2030 } 2031 2032 // Step 5. 2033 return SetProperty(cx, thisObj, prop, value); 2034 } 2035 2036 // https://tc39.es/proposal-iterator-helpers/#sec-get-iteratorprototype-@@tostringtag 2037 static bool toStringTagGetter(JSContext* cx, unsigned argc, Value* vp) { 2038 CallArgs args = CallArgsFromVp(argc, vp); 2039 2040 // Step 1. 2041 args.rval().setString(cx->names().Iterator); 2042 return true; 2043 } 2044 2045 // https://tc39.es/proposal-iterator-helpers/#sec-set-iteratorprototype-@@tostringtag 2046 static bool toStringTagSetter(JSContext* cx, unsigned argc, Value* vp) { 2047 CallArgs args = CallArgsFromVp(argc, vp); 2048 2049 // Step 1. 2050 Rooted<PropertyKey> prop( 2051 cx, PropertyKey::Symbol(cx->wellKnownSymbols().toStringTag)); 2052 if (!SetterThatIgnoresPrototypeProperties(cx, args.thisv(), prop, 2053 args.get(0))) { 2054 return false; 2055 } 2056 2057 // Step 2. 2058 args.rval().setUndefined(); 2059 return true; 2060 } 2061 2062 // https://tc39.es/proposal-iterator-helpers/#sec-get-iteratorprototype-constructor 2063 static bool constructorGetter(JSContext* cx, unsigned argc, Value* vp) { 2064 CallArgs args = CallArgsFromVp(argc, vp); 2065 2066 // Step 1. 2067 JSObject* constructor = 2068 GlobalObject::getOrCreateConstructor(cx, JSProto_Iterator); 2069 if (!constructor) { 2070 return false; 2071 } 2072 args.rval().setObject(*constructor); 2073 return true; 2074 } 2075 2076 // https://tc39.es/proposal-iterator-helpers/#sec-set-iteratorprototype-constructor 2077 static bool constructorSetter(JSContext* cx, unsigned argc, Value* vp) { 2078 CallArgs args = CallArgsFromVp(argc, vp); 2079 2080 // Step 1. 2081 Rooted<PropertyKey> prop(cx, NameToId(cx->names().constructor)); 2082 if (!SetterThatIgnoresPrototypeProperties(cx, args.thisv(), prop, 2083 args.get(0))) { 2084 return false; 2085 } 2086 2087 // Step 2. 2088 args.rval().setUndefined(); 2089 return true; 2090 } 2091 2092 static const JSPropertySpec iterator_properties[] = { 2093 // NOTE: Contrary to most other @@toStringTag properties, this property 2094 // has a special setter (and a getter). 2095 JS_SYM_GETSET(toStringTag, toStringTagGetter, toStringTagSetter, 0), 2096 JS_PS_END, 2097 }; 2098 2099 /* static */ 2100 template <GlobalObject::ProtoKind Kind, const JSClass* ProtoClass, 2101 const JSFunctionSpec* Methods, const bool needsFuseProperty> 2102 bool GlobalObject::initObjectIteratorProto(JSContext* cx, 2103 Handle<GlobalObject*> global, 2104 Handle<JSAtom*> tag) { 2105 if (global->hasBuiltinProto(Kind)) { 2106 return true; 2107 } 2108 2109 RootedObject iteratorProto( 2110 cx, GlobalObject::getOrCreateIteratorPrototype(cx, global)); 2111 if (!iteratorProto) { 2112 return false; 2113 } 2114 2115 RootedObject proto(cx, GlobalObject::createBlankPrototypeInheriting( 2116 cx, ProtoClass, iteratorProto)); 2117 if (!proto || !DefinePropertiesAndFunctions(cx, proto, nullptr, Methods) || 2118 (tag && !DefineToStringTag(cx, proto, tag))) { 2119 return false; 2120 } 2121 2122 if constexpr (needsFuseProperty) { 2123 if (!JSObject::setHasRealmFuseProperty(cx, proto)) { 2124 return false; 2125 } 2126 } 2127 2128 global->initBuiltinProto(Kind, proto); 2129 return true; 2130 } 2131 2132 /* static */ 2133 NativeObject* GlobalObject::getOrCreateArrayIteratorPrototype( 2134 JSContext* cx, Handle<GlobalObject*> global) { 2135 return MaybeNativeObject(getOrCreateBuiltinProto( 2136 cx, global, ProtoKind::ArrayIteratorProto, 2137 cx->names().Array_Iterator_.toHandle(), 2138 initObjectIteratorProto< 2139 ProtoKind::ArrayIteratorProto, &ArrayIteratorPrototypeClass, 2140 array_iterator_methods, /* hasFuseProperty= */ true>)); 2141 } 2142 2143 /* static */ 2144 JSObject* GlobalObject::getOrCreateStringIteratorPrototype( 2145 JSContext* cx, Handle<GlobalObject*> global) { 2146 return getOrCreateBuiltinProto( 2147 cx, global, ProtoKind::StringIteratorProto, 2148 cx->names().String_Iterator_.toHandle(), 2149 initObjectIteratorProto<ProtoKind::StringIteratorProto, 2150 &StringIteratorPrototypeClass, 2151 string_iterator_methods>); 2152 } 2153 2154 /* static */ 2155 JSObject* GlobalObject::getOrCreateRegExpStringIteratorPrototype( 2156 JSContext* cx, Handle<GlobalObject*> global) { 2157 return getOrCreateBuiltinProto( 2158 cx, global, ProtoKind::RegExpStringIteratorProto, 2159 cx->names().RegExp_String_Iterator_.toHandle(), 2160 initObjectIteratorProto<ProtoKind::RegExpStringIteratorProto, 2161 &RegExpStringIteratorPrototypeClass, 2162 regexp_string_iterator_methods>); 2163 } 2164 2165 #ifdef NIGHTLY_BUILD 2166 /* static */ 2167 JSObject* GlobalObject::getOrCreateIteratorRangePrototype( 2168 JSContext* cx, Handle<GlobalObject*> global) { 2169 return getOrCreateBuiltinProto( 2170 cx, global, ProtoKind::IteratorRangeProto, 2171 cx->names().RegExp_String_Iterator_.toHandle(), 2172 initObjectIteratorProto<ProtoKind::IteratorRangeProto, 2173 &IteratorRangePrototypeClass, 2174 iterator_range_methods>); 2175 } 2176 #endif 2177 2178 // Iterator Helper Proposal 2.1.3.1 Iterator() 2179 // https://tc39.es/proposal-iterator-helpers/#sec-iterator as of revision 2180 // ed6e15a 2181 static bool IteratorConstructor(JSContext* cx, unsigned argc, Value* vp) { 2182 CallArgs args = CallArgsFromVp(argc, vp); 2183 2184 // Step 1. 2185 if (!ThrowIfNotConstructing(cx, args, "Iterator")) { 2186 return false; 2187 } 2188 // Throw TypeError if NewTarget is the active function object, preventing the 2189 // Iterator constructor from being used directly. 2190 if (args.callee() == args.newTarget().toObject()) { 2191 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 2192 JSMSG_BOGUS_CONSTRUCTOR, "Iterator"); 2193 return false; 2194 } 2195 2196 // Step 2. 2197 RootedObject proto(cx); 2198 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Iterator, &proto)) { 2199 return false; 2200 } 2201 2202 JSObject* obj = NewObjectWithClassProto<IteratorObject>(cx, proto); 2203 if (!obj) { 2204 return false; 2205 } 2206 2207 args.rval().setObject(*obj); 2208 return true; 2209 } 2210 2211 static const ClassSpec IteratorObjectClassSpec = { 2212 GenericCreateConstructor<IteratorConstructor, 0, gc::AllocKind::FUNCTION>, 2213 GenericCreatePrototype<IteratorObject>, 2214 iterator_static_methods, 2215 nullptr, 2216 iterator_methods, 2217 iterator_properties, 2218 IteratorObject::finishInit, 2219 }; 2220 2221 const JSClass IteratorObject::class_ = { 2222 "Iterator", 2223 JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator), 2224 JS_NULL_CLASS_OPS, 2225 &IteratorObjectClassSpec, 2226 }; 2227 2228 const JSClass IteratorObject::protoClass_ = { 2229 "Iterator.prototype", 2230 JSCLASS_HAS_CACHED_PROTO(JSProto_Iterator), 2231 JS_NULL_CLASS_OPS, 2232 &IteratorObjectClassSpec, 2233 }; 2234 2235 /* static */ bool IteratorObject::finishInit(JSContext* cx, HandleObject ctor, 2236 HandleObject proto) { 2237 Rooted<PropertyKey> id(cx, NameToId(cx->names().constructor)); 2238 return JS_DefinePropertyById(cx, proto, id, constructorGetter, 2239 constructorSetter, 0); 2240 } 2241 2242 // Set up WrapForValidIteratorObject class and its prototype. 2243 static const JSFunctionSpec wrap_for_valid_iterator_methods[] = { 2244 JS_SELF_HOSTED_FN("next", "WrapForValidIteratorNext", 0, 0), 2245 JS_SELF_HOSTED_FN("return", "WrapForValidIteratorReturn", 0, 0), 2246 JS_FS_END, 2247 }; 2248 2249 static const JSClass WrapForValidIteratorPrototypeClass = { 2250 "Wrap For Valid Iterator", 2251 0, 2252 }; 2253 2254 const JSClass WrapForValidIteratorObject::class_ = { 2255 "Wrap For Valid Iterator", 2256 JSCLASS_HAS_RESERVED_SLOTS(WrapForValidIteratorObject::SlotCount), 2257 }; 2258 2259 /* static */ 2260 NativeObject* GlobalObject::getOrCreateWrapForValidIteratorPrototype( 2261 JSContext* cx, Handle<GlobalObject*> global) { 2262 return MaybeNativeObject(getOrCreateBuiltinProto( 2263 cx, global, ProtoKind::WrapForValidIteratorProto, 2264 Handle<JSAtom*>(nullptr), 2265 initObjectIteratorProto<ProtoKind::WrapForValidIteratorProto, 2266 &WrapForValidIteratorPrototypeClass, 2267 wrap_for_valid_iterator_methods>)); 2268 } 2269 2270 WrapForValidIteratorObject* js::NewWrapForValidIterator(JSContext* cx) { 2271 RootedObject proto(cx, GlobalObject::getOrCreateWrapForValidIteratorPrototype( 2272 cx, cx->global())); 2273 if (!proto) { 2274 return nullptr; 2275 } 2276 return NewObjectWithGivenProto<WrapForValidIteratorObject>(cx, proto); 2277 } 2278 2279 // Common iterator object returned by Iterator Helper methods. 2280 static const JSFunctionSpec iterator_helper_methods[] = { 2281 JS_SELF_HOSTED_FN("next", "IteratorHelperNext", 0, 0), 2282 JS_SELF_HOSTED_FN("return", "IteratorHelperReturn", 0, 0), 2283 JS_FS_END, 2284 }; 2285 2286 static const JSClass IteratorHelperPrototypeClass = { 2287 "Iterator Helper", 2288 0, 2289 }; 2290 2291 const JSClass IteratorHelperObject::class_ = { 2292 "Iterator Helper", 2293 JSCLASS_HAS_RESERVED_SLOTS(IteratorHelperObject::SlotCount), 2294 }; 2295 2296 #ifdef NIGHTLY_BUILD 2297 const JSClass IteratorRangeObject::class_ = { 2298 "IteratorRange", 2299 JSCLASS_HAS_RESERVED_SLOTS(IteratorRangeSlotCount), 2300 }; 2301 #endif 2302 2303 /* static */ 2304 NativeObject* GlobalObject::getOrCreateIteratorHelperPrototype( 2305 JSContext* cx, Handle<GlobalObject*> global) { 2306 return MaybeNativeObject(getOrCreateBuiltinProto( 2307 cx, global, ProtoKind::IteratorHelperProto, 2308 cx->names().Iterator_Helper_.toHandle(), 2309 initObjectIteratorProto<ProtoKind::IteratorHelperProto, 2310 &IteratorHelperPrototypeClass, 2311 iterator_helper_methods>)); 2312 } 2313 2314 IteratorHelperObject* js::NewIteratorHelper(JSContext* cx) { 2315 RootedObject proto( 2316 cx, GlobalObject::getOrCreateIteratorHelperPrototype(cx, cx->global())); 2317 if (!proto) { 2318 return nullptr; 2319 } 2320 return NewObjectWithGivenProto<IteratorHelperObject>(cx, proto); 2321 } 2322 2323 bool js::IterableToArray(JSContext* cx, HandleValue iterable, 2324 MutableHandle<ArrayObject*> array) { 2325 JS::ForOfIterator iterator(cx); 2326 if (!iterator.init(iterable, JS::ForOfIterator::ThrowOnNonIterable)) { 2327 return false; 2328 } 2329 2330 array.set(NewDenseEmptyArray(cx)); 2331 if (!array) { 2332 return false; 2333 } 2334 2335 RootedValue nextValue(cx); 2336 while (true) { 2337 bool done; 2338 if (!iterator.next(&nextValue, &done)) { 2339 return false; 2340 } 2341 if (done) { 2342 break; 2343 } 2344 2345 if (!NewbornArrayPush(cx, array, nextValue)) { 2346 return false; 2347 } 2348 } 2349 return true; 2350 } 2351 2352 bool js::HasOptimizableArrayIteratorPrototype(JSContext* cx) { 2353 // Return true if %ArrayIteratorPrototype% does not have (re)defined `next` 2354 // and `return` properties. 2355 return cx->realm()->realmFuses.optimizeArrayIteratorPrototypeFuse.intact(); 2356 } 2357 2358 template <MustBePacked Packed> 2359 bool js::IsArrayWithDefaultIterator(JSObject* obj, JSContext* cx) { 2360 if constexpr (Packed == MustBePacked::Yes) { 2361 if (!IsPackedArray(obj)) { 2362 return false; 2363 } 2364 } else { 2365 if (!obj->is<ArrayObject>()) { 2366 return false; 2367 } 2368 } 2369 ArrayObject* arr = &obj->as<ArrayObject>(); 2370 2371 // Ensure Array.prototype[@@iterator] and %ArrayIteratorPrototype% haven't 2372 // been mutated in a way that affects the iterator protocol. 2373 if (!arr->realm()->realmFuses.optimizeGetIteratorFuse.intact()) { 2374 return false; 2375 } 2376 2377 // Ensure the array has Array.prototype as prototype and doesn't have an own 2378 // @@iterator property. 2379 // 2380 // Most arrays have the default array shape so we have a fast path for this 2381 // case. 2382 GlobalObject& global = arr->global(); 2383 if (arr->shape() == global.maybeArrayShapeWithDefaultProto()) { 2384 return true; 2385 } 2386 2387 NativeObject* arrayProto = global.maybeGetArrayPrototype(); 2388 if (!arrayProto || arr->staticPrototype() != arrayProto) { 2389 return false; 2390 } 2391 if (arr->containsPure(PropertyKey::Symbol(cx->wellKnownSymbols().iterator))) { 2392 return false; 2393 } 2394 2395 return true; 2396 } 2397 2398 template bool js::IsArrayWithDefaultIterator<MustBePacked::No>(JSObject* obj, 2399 JSContext* cx); 2400 template bool js::IsArrayWithDefaultIterator<MustBePacked::Yes>(JSObject* obj, 2401 JSContext* cx); 2402 2403 template <typename ObjectT, JSProtoKey ProtoKey> 2404 static bool IsMapOrSetObjectWithDefaultIterator(JSObject* objArg, 2405 JSContext* cx) { 2406 if (!objArg->is<ObjectT>()) { 2407 return false; 2408 } 2409 auto* obj = &objArg->as<ObjectT>(); 2410 2411 // Ensure {Map,Set}.prototype[@@iterator] and %{Map,Set}IteratorPrototype% 2412 // haven't been mutated in a way that affects the iterator protocol. 2413 // 2414 // Note: this doesn't guard against `return` properties on the iterator 2415 // prototype, so this should only be used in places where we don't have to 2416 // call `IteratorClose`. 2417 if constexpr (std::is_same_v<ObjectT, MapObject>) { 2418 if (!obj->realm()->realmFuses.optimizeMapObjectIteratorFuse.intact()) { 2419 return false; 2420 } 2421 } else { 2422 static_assert(std::is_same_v<ObjectT, SetObject>); 2423 if (!obj->realm()->realmFuses.optimizeSetObjectIteratorFuse.intact()) { 2424 return false; 2425 } 2426 } 2427 2428 // Ensure the object has the builtin prototype as proto. 2429 GlobalObject& global = obj->global(); 2430 JSObject* proto = global.maybeGetPrototype(ProtoKey); 2431 if (!proto || obj->staticPrototype() != proto) { 2432 return false; 2433 } 2434 2435 // Ensure the Map or Set object doesn't have an own @@iterator property. 2436 // Most Maps and Sets have no own properties so we have a fast path for this 2437 // case. 2438 if (obj->empty()) { 2439 return true; 2440 } 2441 if (obj->containsPure(PropertyKey::Symbol(cx->wellKnownSymbols().iterator))) { 2442 return false; 2443 } 2444 return true; 2445 } 2446 bool js::IsMapObjectWithDefaultIterator(JSObject* obj, JSContext* cx) { 2447 return IsMapOrSetObjectWithDefaultIterator<MapObject, JSProto_Map>(obj, cx); 2448 } 2449 2450 bool js::IsSetObjectWithDefaultIterator(JSObject* obj, JSContext* cx) { 2451 return IsMapOrSetObjectWithDefaultIterator<SetObject, JSProto_Set>(obj, cx); 2452 }