MapObject.cpp (63176B)
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 #include "builtin/MapObject-inl.h" 8 #include "builtin/MapObject.h" 9 10 #include "jsapi.h" 11 12 #include "builtin/OrderedHashTableObject.h" 13 #include "gc/GCContext.h" 14 #include "jit/InlinableNatives.h" 15 #include "js/MapAndSet.h" 16 #include "js/PropertyAndElement.h" // JS_DefineFunctions 17 #include "js/PropertySpec.h" 18 #include "js/Utility.h" 19 #include "vm/BigIntType.h" 20 #include "vm/EqualityOperations.h" // js::SameValue 21 #include "vm/GlobalObject.h" 22 #include "vm/Interpreter.h" 23 #include "vm/JSContext.h" 24 #include "vm/JSObject.h" 25 #include "vm/SelfHosting.h" 26 #include "vm/SymbolType.h" 27 28 #include "builtin/OrderedHashTableObject-inl.h" 29 #include "gc/GCContext-inl.h" 30 #include "gc/Marking-inl.h" 31 #include "gc/ObjectKind-inl.h" 32 #include "vm/GeckoProfiler-inl.h" 33 #include "vm/NativeObject-inl.h" 34 35 using namespace js; 36 37 using mozilla::NumberEqualsInt32; 38 39 /*** HashableValue **********************************************************/ 40 41 static PreBarriered<Value> NormalizeDoubleValue(double d) { 42 int32_t i; 43 if (NumberEqualsInt32(d, &i)) { 44 // Normalize int32_t-valued doubles to int32_t for faster hashing and 45 // testing. Note: we use NumberEqualsInt32 here instead of NumberIsInt32 46 // because we want -0 and 0 to be normalized to the same thing. 47 return Int32Value(i); 48 } 49 50 // Normalize the sign bit of a NaN. 51 return JS::CanonicalizedDoubleValue(d); 52 } 53 54 bool HashableValue::setValue(JSContext* cx, const Value& v) { 55 if (v.isString()) { 56 // Atomize so that hash() and operator==() are fast and infallible. 57 JSString* str = AtomizeString(cx, v.toString()); 58 if (!str) { 59 return false; 60 } 61 value = StringValue(str); 62 } else if (v.isDouble()) { 63 value = NormalizeDoubleValue(v.toDouble()); 64 } else { 65 value = v; 66 } 67 68 MOZ_ASSERT(value.isUndefined() || value.isNull() || value.isBoolean() || 69 value.isNumber() || value.isString() || value.isSymbol() || 70 value.isObject() || value.isBigInt()); 71 return true; 72 } 73 74 static HashNumber HashValue(const Value& v, 75 const mozilla::HashCodeScrambler& hcs) { 76 // HashableValue::setValue normalizes values so that the SameValue relation 77 // on HashableValues is the same as the == relationship on 78 // value.asRawBits(). So why not just return that? Security. 79 // 80 // To avoid revealing GC of atoms, string-based hash codes are computed 81 // from the string contents rather than any pointer; to avoid revealing 82 // addresses, pointer-based hash codes are computed using the 83 // HashCodeScrambler. 84 85 if (v.isString()) { 86 return v.toString()->asAtom().hash(); 87 } 88 if (v.isSymbol()) { 89 return v.toSymbol()->hash(); 90 } 91 if (v.isBigInt()) { 92 return MaybeForwarded(v.toBigInt())->hash(); 93 } 94 if (v.isObject()) { 95 return hcs.scramble(v.asRawBits()); 96 } 97 98 MOZ_ASSERT(!v.isGCThing(), "do not reveal pointers via hash codes"); 99 return mozilla::HashGeneric(v.asRawBits()); 100 } 101 102 HashNumber HashableValue::hash(const mozilla::HashCodeScrambler& hcs) const { 103 return HashValue(value, hcs); 104 } 105 106 bool HashableValue::equals(const HashableValue& other) const { 107 // Two HashableValues are equal if they have equal bits. 108 bool b = (value.asRawBits() == other.value.asRawBits()); 109 110 if (!b && (value.type() == other.value.type())) { 111 if (value.isBigInt()) { 112 // BigInt values are considered equal if they represent the same 113 // mathematical value. 114 b = BigInt::equal(value.toBigInt(), other.value.toBigInt()); 115 } 116 } 117 118 #ifdef DEBUG 119 bool same; 120 JSContext* cx = TlsContext.get(); 121 MOZ_ASSERT(SameValueZero(cx, value, other.value, &same)); 122 MOZ_ASSERT(same == b); 123 #endif 124 return b; 125 } 126 127 /*** MapIterator ************************************************************/ 128 129 namespace {} /* anonymous namespace */ 130 131 static const JSClassOps MapIteratorObjectClassOps = { 132 nullptr, // addProperty 133 nullptr, // delProperty 134 nullptr, // enumerate 135 nullptr, // newEnumerate 136 nullptr, // resolve 137 nullptr, // mayResolve 138 MapIteratorObject::finalize, // finalize 139 nullptr, // call 140 nullptr, // construct 141 nullptr, // trace 142 }; 143 144 static const ClassExtension MapIteratorObjectClassExtension = { 145 MapIteratorObject::objectMoved, // objectMovedOp 146 }; 147 148 const JSClass MapIteratorObject::class_ = { 149 "Map Iterator", 150 JSCLASS_HAS_RESERVED_SLOTS(MapIteratorObject::SlotCount) | 151 JSCLASS_FOREGROUND_FINALIZE | JSCLASS_SKIP_NURSERY_FINALIZE, 152 &MapIteratorObjectClassOps, 153 JS_NULL_CLASS_SPEC, 154 &MapIteratorObjectClassExtension, 155 }; 156 157 const JSFunctionSpec MapIteratorObject::methods[] = { 158 JS_SELF_HOSTED_FN("next", "MapIteratorNext", 0, 0), 159 JS_FS_END, 160 }; 161 162 /* static */ 163 bool GlobalObject::initMapIteratorProto(JSContext* cx, 164 Handle<GlobalObject*> global) { 165 Rooted<JSObject*> base( 166 cx, GlobalObject::getOrCreateIteratorPrototype(cx, global)); 167 if (!base) { 168 return false; 169 } 170 Rooted<PlainObject*> proto( 171 cx, GlobalObject::createBlankPrototypeInheriting<PlainObject>(cx, base)); 172 if (!proto) { 173 return false; 174 } 175 if (!JS_DefineFunctions(cx, proto, MapIteratorObject::methods) || 176 !DefineToStringTag(cx, proto, cx->names().Map_Iterator_)) { 177 return false; 178 } 179 if (!JSObject::setHasRealmFuseProperty(cx, proto)) { 180 return false; 181 } 182 global->initBuiltinProto(ProtoKind::MapIteratorProto, proto); 183 return true; 184 } 185 186 template <typename TableObject> 187 static inline bool HasRegisteredNurseryIterators(TableObject* t) { 188 Value v = t->getReservedSlot(TableObject::RegisteredNurseryIteratorsSlot); 189 return v.toBoolean(); 190 } 191 192 template <typename TableObject> 193 static inline void SetRegisteredNurseryIterators(TableObject* t, bool b) { 194 t->setReservedSlot(TableObject::RegisteredNurseryIteratorsSlot, 195 JS::BooleanValue(b)); 196 } 197 198 MapIteratorObject* MapIteratorObject::create(JSContext* cx, 199 Handle<MapObject*> mapobj, 200 Kind kind) { 201 Rooted<GlobalObject*> global(cx, &mapobj->global()); 202 Rooted<JSObject*> proto( 203 cx, GlobalObject::getOrCreateMapIteratorPrototype(cx, global)); 204 if (!proto) { 205 return nullptr; 206 } 207 208 MapIteratorObject* iterobj = 209 NewObjectWithGivenProto<MapIteratorObject>(cx, proto); 210 if (!iterobj) { 211 return nullptr; 212 } 213 214 if (IsInsideNursery(iterobj) && 215 !HasRegisteredNurseryIterators(mapobj.get())) { 216 if (!cx->nursery().addMapWithNurseryIterators(mapobj)) { 217 ReportOutOfMemory(cx); 218 return nullptr; 219 } 220 SetRegisteredNurseryIterators(mapobj.get(), true); 221 } 222 223 MapObject::Table(mapobj).initIterator(iterobj, kind); 224 225 return iterobj; 226 } 227 228 void MapIteratorObject::finalize(JS::GCContext* gcx, JSObject* obj) { 229 MOZ_ASSERT(gcx->onMainThread()); 230 MOZ_ASSERT(!IsInsideNursery(obj)); 231 if (obj->as<MapIteratorObject>().isActive()) { 232 obj->as<MapIteratorObject>().unlink(); 233 } 234 } 235 236 size_t MapIteratorObject::objectMoved(JSObject* obj, JSObject* old) { 237 MapIteratorObject* iter = &obj->as<MapIteratorObject>(); 238 if (!iter->isActive()) { 239 return 0; 240 } 241 if (IsInsideNursery(old)) { 242 MapObject* mapObj = iter->target(); 243 MapObject::Table(mapObj).relinkNurseryIterator(iter); 244 } else { 245 iter->updateListAfterMove(&old->as<MapIteratorObject>()); 246 } 247 return 0; 248 } 249 250 MapObject* MapIteratorObject::target() const { 251 MOZ_ASSERT(isActive(), "only active iterators have a target object"); 252 Value value = getFixedSlot(TargetSlot); 253 return &MaybeForwarded(&value.toObject())->as<MapObject>(); 254 } 255 256 bool MapIteratorObject::next(MapIteratorObject* mapIterator, 257 ArrayObject* resultPairObj) { 258 // IC code calls this directly. 259 AutoUnsafeCallWithABI unsafe; 260 261 // Check invariants for inlined GetNextMapEntryForIterator. 262 263 // The array should be tenured, so that post-barrier can be done simply. 264 MOZ_ASSERT(resultPairObj->isTenured()); 265 266 // The array elements should be fixed. 267 MOZ_ASSERT(resultPairObj->hasFixedElements()); 268 MOZ_ASSERT(resultPairObj->getDenseInitializedLength() == 2); 269 MOZ_ASSERT(resultPairObj->getDenseCapacity() >= 2); 270 271 if (!mapIterator->isActive()) { 272 // Already done. 273 return true; 274 } 275 276 auto storeResult = [resultPairObj](Kind kind, const auto& element) { 277 switch (kind) { 278 case Kind::Keys: 279 resultPairObj->setDenseElement(0, element.key.get()); 280 break; 281 282 case Kind::Values: 283 resultPairObj->setDenseElement(1, element.value); 284 break; 285 286 case Kind::Entries: { 287 resultPairObj->setDenseElement(0, element.key.get()); 288 resultPairObj->setDenseElement(1, element.value); 289 break; 290 } 291 } 292 }; 293 MapObject* mapObj = mapIterator->target(); 294 return MapObject::Table(mapObj).iteratorNext(mapIterator, storeResult); 295 } 296 297 /* static */ 298 JSObject* MapIteratorObject::createResultPair(JSContext* cx) { 299 ArrayObject* resultPairObj = 300 NewDenseFullyAllocatedArray(cx, 2, TenuredObject); 301 if (!resultPairObj) { 302 return nullptr; 303 } 304 305 resultPairObj->setDenseInitializedLength(2); 306 resultPairObj->initDenseElement(0, NullValue()); 307 resultPairObj->initDenseElement(1, NullValue()); 308 309 return resultPairObj; 310 } 311 312 /*** Map ********************************************************************/ 313 314 struct js::UnbarrieredHashPolicy { 315 using Lookup = Value; 316 static HashNumber hash(const Lookup& v, 317 const mozilla::HashCodeScrambler& hcs) { 318 return HashValue(v, hcs); 319 } 320 static bool match(const Value& k, const Lookup& l) { return k == l; } 321 static bool isEmpty(const Value& v) { return v.isMagic(JS_HASH_KEY_EMPTY); } 322 static void makeEmpty(Value* vp) { vp->setMagic(JS_HASH_KEY_EMPTY); } 323 }; 324 325 // MapObject::Table, ::UnbarrieredTable and ::PreBarrieredTable must all have 326 // the same memory layout. 327 static_assert(sizeof(MapObject::Table::Entry) == 328 sizeof(MapObject::UnbarrieredTable::Entry)); 329 static_assert(sizeof(MapObject::Table::Entry) == 330 sizeof(MapObject::PreBarrieredTable::Entry)); 331 332 const JSClassOps MapObject::classOps_ = { 333 nullptr, // addProperty 334 nullptr, // delProperty 335 nullptr, // enumerate 336 nullptr, // newEnumerate 337 nullptr, // resolve 338 nullptr, // mayResolve 339 nullptr, // finalize 340 nullptr, // call 341 nullptr, // construct 342 trace, // trace 343 }; 344 345 const ClassSpec MapObject::classSpec_ = { 346 GenericCreateConstructor<MapObject::construct, 0, gc::AllocKind::FUNCTION, 347 &jit::JitInfo_MapConstructor>, 348 GenericCreatePrototype<MapObject>, 349 MapObject::staticMethods, 350 MapObject::staticProperties, 351 MapObject::methods, 352 MapObject::properties, 353 MapObject::finishInit, 354 }; 355 356 const ClassExtension MapObject::classExtension_ = { 357 MapObject::objectMoved, // objectMovedOp 358 }; 359 360 const JSClass MapObject::class_ = { 361 "Map", 362 JSCLASS_DELAY_METADATA_BUILDER | 363 JSCLASS_HAS_RESERVED_SLOTS(MapObject::SlotCount) | 364 JSCLASS_HAS_CACHED_PROTO(JSProto_Map), 365 &MapObject::classOps_, &MapObject::classSpec_, &MapObject::classExtension_}; 366 367 const JSClass MapObject::protoClass_ = { 368 "Map.prototype", 369 JSCLASS_HAS_CACHED_PROTO(JSProto_Map), 370 JS_NULL_CLASS_OPS, 371 &MapObject::classSpec_, 372 }; 373 374 const JSPropertySpec MapObject::properties[] = { 375 JS_INLINABLE_PSG("size", size, 0, MapSize), 376 JS_STRING_SYM_PS(toStringTag, "Map", JSPROP_READONLY), 377 JS_PS_END, 378 }; 379 380 const JSFunctionSpec MapObject::methods[] = { 381 JS_INLINABLE_FN("get", get, 1, 0, MapGet), 382 JS_INLINABLE_FN("has", has, 1, 0, MapHas), 383 JS_INLINABLE_FN("set", set, 2, 0, MapSet), 384 JS_INLINABLE_FN("delete", delete_, 1, 0, MapDelete), 385 JS_FN("keys", keys, 0, 0), 386 JS_FN("values", values, 0, 0), 387 JS_FN("clear", clear, 0, 0), 388 JS_SELF_HOSTED_FN("forEach", "MapForEach", 2, 0), 389 JS_FN("getOrInsert", getOrInsert, 2, 0), 390 JS_SELF_HOSTED_FN("getOrInsertComputed", "MapGetOrInsertComputed", 2, 0), 391 JS_FN("entries", entries, 0, 0), 392 // @@iterator is re-defined in finishInit so that it has the 393 // same identity as |entries|. 394 JS_SYM_FN(iterator, entries, 0, 0), 395 JS_FS_END, 396 }; 397 398 const JSPropertySpec MapObject::staticProperties[] = { 399 JS_SELF_HOSTED_SYM_GET(species, "$MapSpecies", 0), 400 JS_PS_END, 401 }; 402 403 const JSFunctionSpec MapObject::staticMethods[] = { 404 JS_SELF_HOSTED_FN("groupBy", "MapGroupBy", 2, 0), 405 JS_FS_END, 406 }; 407 408 /* static */ bool MapObject::finishInit(JSContext* cx, HandleObject ctor, 409 HandleObject proto) { 410 Handle<NativeObject*> nativeProto = proto.as<NativeObject>(); 411 412 RootedValue entriesFn(cx); 413 RootedId entriesId(cx, NameToId(cx->names().entries)); 414 if (!NativeGetProperty(cx, nativeProto, entriesId, &entriesFn)) { 415 return false; 416 } 417 418 // 23.1.3.12 Map.prototype[@@iterator]() 419 // The initial value of the @@iterator property is the same function object 420 // as the initial value of the "entries" property. 421 RootedId iteratorId(cx, PropertyKey::Symbol(cx->wellKnownSymbols().iterator)); 422 if (!NativeDefineDataProperty(cx, nativeProto, iteratorId, entriesFn, 0)) { 423 return false; 424 } 425 426 return JSObject::setHasRealmFuseProperty(cx, nativeProto); 427 } 428 429 void MapObject::trace(JSTracer* trc, JSObject* obj) { 430 MapObject* mapObj = &obj->as<MapObject>(); 431 Table(mapObj).trace(trc); 432 } 433 434 using NurseryKeysVector = GCVector<Value, 4, SystemAllocPolicy>; 435 436 template <typename TableObject> 437 static NurseryKeysVector* GetNurseryKeys(TableObject* t) { 438 Value value = t->getReservedSlot(TableObject::NurseryKeysSlot); 439 return reinterpret_cast<NurseryKeysVector*>(value.toPrivate()); 440 } 441 442 template <typename TableObject> 443 static NurseryKeysVector* AllocNurseryKeys(TableObject* t) { 444 MOZ_ASSERT(!GetNurseryKeys(t)); 445 auto keys = js_new<NurseryKeysVector>(); 446 if (!keys) { 447 return nullptr; 448 } 449 450 t->setReservedSlot(TableObject::NurseryKeysSlot, PrivateValue(keys)); 451 return keys; 452 } 453 454 template <typename TableObject> 455 static void DeleteNurseryKeys(TableObject* t) { 456 auto keys = GetNurseryKeys(t); 457 MOZ_ASSERT(keys); 458 js_delete(keys); 459 t->setReservedSlot(TableObject::NurseryKeysSlot, PrivateValue(nullptr)); 460 } 461 462 // A generic store buffer entry that traces all nursery keys for an ordered hash 463 // map or set. 464 template <typename ObjectT> 465 class js::OrderedHashTableRef : public gc::BufferableRef { 466 ObjectT* object; 467 468 public: 469 explicit OrderedHashTableRef(ObjectT* obj) : object(obj) {} 470 471 void trace(JSTracer* trc) override { 472 MOZ_ASSERT(!IsInsideNursery(object)); 473 NurseryKeysVector* keys = GetNurseryKeys(object); 474 MOZ_ASSERT(keys); 475 476 keys->mutableEraseIf([&](Value& key) { 477 MOZ_ASSERT(typename ObjectT::UnbarrieredTable(object).hash(key) == 478 typename ObjectT::Table(object).hash( 479 *reinterpret_cast<const HashableValue*>(&key))); 480 MOZ_ASSERT(IsInsideNursery(key.toGCThing())); 481 482 auto result = typename ObjectT::UnbarrieredTable(object).rekeyOneEntry( 483 key, [trc](const Value& prior) { 484 Value key = prior; 485 TraceManuallyBarrieredEdge(trc, &key, "ordered hash table key"); 486 return key; 487 }); 488 489 if (result.isNothing()) { 490 return true; // Key removed. 491 } 492 493 key = result.value(); 494 return !IsInsideNursery(key.toGCThing()); 495 }); 496 497 if (!keys->empty()) { 498 trc->runtime()->gc.storeBuffer().putGeneric( 499 OrderedHashTableRef<ObjectT>(object)); 500 return; 501 } 502 503 DeleteNurseryKeys(object); 504 } 505 }; 506 507 template <typename ObjectT> 508 [[nodiscard]] inline static bool PostWriteBarrier(ObjectT* obj, 509 const Value& keyValue) { 510 MOZ_ASSERT(!IsInsideNursery(obj)); 511 512 if (MOZ_LIKELY(!keyValue.isObject() && !keyValue.isBigInt())) { 513 MOZ_ASSERT_IF(keyValue.isGCThing(), !IsInsideNursery(keyValue.toGCThing())); 514 return true; 515 } 516 517 if (!IsInsideNursery(keyValue.toGCThing())) { 518 return true; 519 } 520 521 NurseryKeysVector* keys = GetNurseryKeys(obj); 522 if (!keys) { 523 keys = AllocNurseryKeys(obj); 524 if (!keys) { 525 return false; 526 } 527 528 keyValue.toGCThing()->storeBuffer()->putGeneric( 529 OrderedHashTableRef<ObjectT>(obj)); 530 } 531 532 return keys->append(keyValue); 533 } 534 535 bool MapObject::getKeysAndValuesInterleaved( 536 JS::MutableHandle<GCVector<JS::Value>> entries) { 537 auto appendEntry = [&entries](auto& entry) { 538 return entries.append(entry.key.get()) && entries.append(entry.value); 539 }; 540 return Table(this).forEachEntry(appendEntry); 541 } 542 543 bool MapObject::set(JSContext* cx, const Value& key, const Value& val) { 544 HashableValue k; 545 if (!k.setValue(cx, key)) { 546 return false; 547 } 548 return setWithHashableKey(cx, k, val); 549 } 550 551 bool MapObject::setWithHashableKey(JSContext* cx, const HashableValue& key, 552 const Value& value) { 553 bool needsPostBarriers = isTenured(); 554 if (needsPostBarriers) { 555 // Use the Table representation which has post barriers. 556 if (!PostWriteBarrier(this, key)) { 557 ReportOutOfMemory(cx); 558 return false; 559 } 560 if (!Table(this).put(cx, key, value)) { 561 return false; 562 } 563 } else { 564 // Use the PreBarrieredTable representation which does not. 565 if (!PreBarrieredTable(this).put(cx, key, value)) { 566 return false; 567 } 568 } 569 570 return true; 571 } 572 573 bool MapObject::getOrInsert(JSContext* cx, const Value& key, const Value& val, 574 MutableHandleValue rval) { 575 HashableValue k; 576 if (!k.setValue(cx, key)) { 577 return false; 578 } 579 580 bool needsPostBarriers = isTenured(); 581 if (needsPostBarriers) { 582 if (!PostWriteBarrier(this, k)) { 583 ReportOutOfMemory(cx); 584 return false; 585 } 586 // Use the Table representation which has post barriers. 587 if (const Table::Entry* p = Table(this).getOrAdd(cx, k, val)) { 588 rval.set(p->value); 589 } else { 590 return false; 591 } 592 } else { 593 // Use the PreBarrieredTable representation which does not. 594 if (const PreBarrieredTable::Entry* p = 595 PreBarrieredTable(this).getOrAdd(cx, k, val)) { 596 rval.set(p->value); 597 } else { 598 return false; 599 } 600 } 601 return true; 602 } 603 604 MapObject* MapObject::createWithProto(JSContext* cx, HandleObject proto, 605 NewObjectKind newKind) { 606 MOZ_ASSERT(proto); 607 608 gc::AllocKind allocKind = gc::GetGCObjectKind(SlotCount); 609 610 AutoSetNewObjectMetadata metadata(cx); 611 auto* mapObj = 612 NewObjectWithGivenProtoAndKinds<MapObject>(cx, proto, allocKind, newKind); 613 if (!mapObj) { 614 return nullptr; 615 } 616 617 UnbarrieredTable(mapObj).initSlots(); 618 mapObj->initReservedSlot(NurseryKeysSlot, PrivateValue(nullptr)); 619 mapObj->initReservedSlot(RegisteredNurseryIteratorsSlot, BooleanValue(false)); 620 return mapObj; 621 } 622 623 MapObject* MapObject::create(JSContext* cx, 624 HandleObject proto /* = nullptr */) { 625 if (proto) { 626 return createWithProto(cx, proto, GenericObject); 627 } 628 629 // This is the common case so use the template object's shape to optimize the 630 // allocation. 631 MapObject* templateObj = GlobalObject::getOrCreateMapTemplateObject(cx); 632 if (!templateObj) { 633 return nullptr; 634 } 635 636 gc::AllocKind allocKind = templateObj->asTenured().getAllocKind(); 637 MOZ_ASSERT(gc::GetGCKindSlots(allocKind) >= SlotCount); 638 MOZ_ASSERT(!gc::IsFinalizedKind(allocKind)); 639 640 AutoSetNewObjectMetadata metadata(cx); 641 Rooted<SharedShape*> shape(cx, templateObj->sharedShape()); 642 auto* mapObj = 643 NativeObject::create<MapObject>(cx, allocKind, gc::Heap::Default, shape); 644 if (!mapObj) { 645 return nullptr; 646 } 647 648 UnbarrieredTable(mapObj).initSlots(); 649 mapObj->initReservedSlot(NurseryKeysSlot, PrivateValue(nullptr)); 650 mapObj->initReservedSlot(RegisteredNurseryIteratorsSlot, BooleanValue(false)); 651 return mapObj; 652 } 653 654 // static 655 MapObject* GlobalObject::getOrCreateMapTemplateObject(JSContext* cx) { 656 GlobalObjectData& data = cx->global()->data(); 657 if (MapObject* obj = data.mapObjectTemplate) { 658 return obj; 659 } 660 661 Rooted<JSObject*> proto(cx, 662 GlobalObject::getOrCreatePrototype(cx, JSProto_Map)); 663 if (!proto) { 664 return nullptr; 665 } 666 auto* mapObj = MapObject::createWithProto(cx, proto, TenuredObject); 667 if (!mapObj) { 668 return nullptr; 669 } 670 671 data.mapObjectTemplate.init(mapObj); 672 return mapObj; 673 } 674 675 size_t MapObject::sizeOfData(mozilla::MallocSizeOf mallocSizeOf) { 676 size_t size = 0; 677 size += Table(this).sizeOfExcludingObject(mallocSizeOf); 678 if (NurseryKeysVector* nurseryKeys = GetNurseryKeys(this)) { 679 size += nurseryKeys->sizeOfIncludingThis(mallocSizeOf); 680 } 681 return size; 682 } 683 684 size_t MapObject::objectMoved(JSObject* obj, JSObject* old) { 685 auto* mapObj = &obj->as<MapObject>(); 686 687 Table(mapObj).updateIteratorsAfterMove(&old->as<MapObject>()); 688 689 if (IsInsideNursery(old)) { 690 Nursery& nursery = mapObj->runtimeFromMainThread()->gc.nursery(); 691 Table(mapObj).maybeMoveBufferOnPromotion(nursery); 692 } 693 694 return 0; 695 } 696 697 void MapObject::clearNurseryIteratorsBeforeMinorGC() { 698 Table(this).clearNurseryIterators(); 699 } 700 701 /* static */ 702 MapObject* MapObject::sweepAfterMinorGC(JS::GCContext* gcx, MapObject* mapobj) { 703 Nursery& nursery = gcx->runtime()->gc.nursery(); 704 bool wasInCollectedRegion = nursery.inCollectedRegion(mapobj); 705 if (wasInCollectedRegion && !IsForwarded(mapobj)) { 706 // This MapObject is dead. 707 return nullptr; 708 } 709 710 mapobj = MaybeForwarded(mapobj); 711 712 // Keep |mapobj| registered with the nursery if it still has nursery 713 // iterators. 714 bool hasNurseryIterators = Table(mapobj).hasNurseryIterators(); 715 SetRegisteredNurseryIterators(mapobj, hasNurseryIterators); 716 return hasNurseryIterators ? mapobj : nullptr; 717 } 718 719 bool MapObject::tryOptimizeCtorWithIterable(JSContext* cx, 720 const Value& iterableVal, 721 bool* optimized) { 722 MOZ_ASSERT(!iterableVal.isNullOrUndefined()); 723 MOZ_ASSERT(!*optimized); 724 725 if (!CanOptimizeMapOrSetCtorWithIterable<JSProto_Map>(MapObject::set, this, 726 cx)) { 727 return true; 728 } 729 730 if (!iterableVal.isObject()) { 731 return true; 732 } 733 JSObject* iterable = &iterableVal.toObject(); 734 735 // Fast path for `new Map(array)`. 736 if (IsOptimizableArrayForMapOrSetCtor<MapOrSet::Map>(iterable, cx)) { 737 ArrayObject* array = &iterable->as<ArrayObject>(); 738 uint32_t len = array->getDenseInitializedLength(); 739 740 for (uint32_t index = 0; index < len; index++) { 741 Value element = array->getDenseElement(index); 742 MOZ_ASSERT(IsPackedArray(&element.toObject())); 743 744 auto* elementArray = &element.toObject().as<ArrayObject>(); 745 Value key = elementArray->getDenseElement(0); 746 Value value = elementArray->getDenseElement(1); 747 748 MOZ_ASSERT(!key.isMagic(JS_ELEMENTS_HOLE)); 749 MOZ_ASSERT(!value.isMagic(JS_ELEMENTS_HOLE)); 750 751 if (!set(cx, key, value)) { 752 return false; 753 } 754 } 755 756 *optimized = true; 757 return true; 758 } 759 760 // Fast path for `new Map(map)`. 761 if (IsMapObjectWithDefaultIterator(iterable, cx)) { 762 auto* iterableMap = &iterable->as<MapObject>(); 763 auto addEntry = [cx, this](auto& entry) { 764 return setWithHashableKey(cx, entry.key, entry.value); 765 }; 766 if (!Table(iterableMap).forEachEntry(addEntry)) { 767 return false; 768 } 769 *optimized = true; 770 return true; 771 } 772 773 return true; 774 } 775 776 // static 777 MapObject* MapObject::createFromIterable(JSContext* cx, Handle<JSObject*> proto, 778 Handle<Value> iterable, 779 Handle<MapObject*> allocatedFromJit) { 780 // A null-or-undefined |iterable| is quite common and we check for this in JIT 781 // code. 782 MOZ_ASSERT_IF(allocatedFromJit, !iterable.isNullOrUndefined()); 783 784 Rooted<MapObject*> obj(cx, allocatedFromJit); 785 if (!obj) { 786 obj = MapObject::create(cx, proto); 787 if (!obj) { 788 return nullptr; 789 } 790 } 791 792 if (!iterable.isNullOrUndefined()) { 793 bool optimized = false; 794 if (!obj->tryOptimizeCtorWithIterable(cx, iterable, &optimized)) { 795 return nullptr; 796 } 797 if (!optimized) { 798 FixedInvokeArgs<1> args(cx); 799 args[0].set(iterable); 800 801 RootedValue thisv(cx, ObjectValue(*obj)); 802 if (!CallSelfHostedFunction(cx, cx->names().MapConstructorInit, thisv, 803 args, args.rval())) { 804 return nullptr; 805 } 806 } 807 } 808 809 return obj; 810 } 811 812 bool MapObject::construct(JSContext* cx, unsigned argc, Value* vp) { 813 AutoJSConstructorProfilerEntry pseudoFrame(cx, "Map"); 814 CallArgs args = CallArgsFromVp(argc, vp); 815 816 if (!ThrowIfNotConstructing(cx, args, "Map")) { 817 return false; 818 } 819 820 RootedObject proto(cx); 821 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Map, &proto)) { 822 return false; 823 } 824 825 MapObject* obj = MapObject::createFromIterable(cx, proto, args.get(0)); 826 if (!obj) { 827 return false; 828 } 829 830 args.rval().setObject(*obj); 831 return true; 832 } 833 834 bool MapObject::is(HandleValue v) { 835 return v.isObject() && v.toObject().hasClass(&class_); 836 } 837 838 bool MapObject::is(HandleObject o) { return o->hasClass(&class_); } 839 840 uint32_t MapObject::size() { 841 static_assert(sizeof(Table(this).count()) <= sizeof(uint32_t), 842 "map count must be precisely representable as a JS number"); 843 return Table(this).count(); 844 } 845 846 bool MapObject::size_impl(JSContext* cx, const CallArgs& args) { 847 auto* mapObj = &args.thisv().toObject().as<MapObject>(); 848 args.rval().setNumber(mapObj->size()); 849 return true; 850 } 851 852 bool MapObject::size(JSContext* cx, unsigned argc, Value* vp) { 853 AutoJSMethodProfilerEntry pseudoFrame(cx, "Map.prototype", "size"); 854 CallArgs args = CallArgsFromVp(argc, vp); 855 return CallNonGenericMethod<MapObject::is, MapObject::size_impl>(cx, args); 856 } 857 858 bool MapObject::get(JSContext* cx, const Value& key, MutableHandleValue rval) { 859 HashableValue k; 860 if (!k.setValue(cx, key)) { 861 return false; 862 } 863 864 if (const Table::Entry* p = Table(this).get(k)) { 865 rval.set(p->value); 866 } else { 867 rval.setUndefined(); 868 } 869 870 return true; 871 } 872 873 bool MapObject::get_impl(JSContext* cx, const CallArgs& args) { 874 auto* mapObj = &args.thisv().toObject().as<MapObject>(); 875 return mapObj->get(cx, args.get(0), args.rval()); 876 } 877 878 bool MapObject::get(JSContext* cx, unsigned argc, Value* vp) { 879 AutoJSMethodProfilerEntry pseudoFrame(cx, "Map.prototype", "get"); 880 CallArgs args = CallArgsFromVp(argc, vp); 881 return CallNonGenericMethod<MapObject::is, MapObject::get_impl>(cx, args); 882 } 883 884 bool MapObject::has(JSContext* cx, const Value& key, bool* rval) { 885 HashableValue k; 886 if (!k.setValue(cx, key)) { 887 return false; 888 } 889 890 *rval = Table(this).has(k); 891 return true; 892 } 893 894 bool MapObject::has_impl(JSContext* cx, const CallArgs& args) { 895 auto* mapObj = &args.thisv().toObject().as<MapObject>(); 896 bool found; 897 if (!mapObj->has(cx, args.get(0), &found)) { 898 return false; 899 } 900 args.rval().setBoolean(found); 901 return true; 902 } 903 904 bool MapObject::has(JSContext* cx, unsigned argc, Value* vp) { 905 AutoJSMethodProfilerEntry pseudoFrame(cx, "Map.prototype", "has"); 906 CallArgs args = CallArgsFromVp(argc, vp); 907 return CallNonGenericMethod<MapObject::is, MapObject::has_impl>(cx, args); 908 } 909 910 bool MapObject::set_impl(JSContext* cx, const CallArgs& args) { 911 auto* mapObj = &args.thisv().toObject().as<MapObject>(); 912 if (!mapObj->set(cx, args.get(0), args.get(1))) { 913 return false; 914 } 915 args.rval().set(args.thisv()); 916 return true; 917 } 918 919 bool MapObject::set(JSContext* cx, unsigned argc, Value* vp) { 920 AutoJSMethodProfilerEntry pseudoFrame(cx, "Map.prototype", "set"); 921 CallArgs args = CallArgsFromVp(argc, vp); 922 return CallNonGenericMethod<MapObject::is, MapObject::set_impl>(cx, args); 923 } 924 925 bool MapObject::getOrInsert_impl(JSContext* cx, const CallArgs& args) { 926 auto* mapObj = &args.thisv().toObject().as<MapObject>(); 927 return mapObj->getOrInsert(cx, args.get(0), args.get(1), args.rval()); 928 } 929 930 bool MapObject::getOrInsert(JSContext* cx, unsigned argc, Value* vp) { 931 AutoJSMethodProfilerEntry pseudoFrame(cx, "Map.prototype", "getOrInsert"); 932 CallArgs args = CallArgsFromVp(argc, vp); 933 return CallNonGenericMethod<MapObject::is, MapObject::getOrInsert_impl>(cx, 934 args); 935 } 936 937 bool MapObject::delete_(JSContext* cx, const Value& key, bool* rval) { 938 HashableValue k; 939 if (!k.setValue(cx, key)) { 940 return false; 941 } 942 943 if (isTenured()) { 944 *rval = Table(this).remove(cx, k); 945 } else { 946 *rval = PreBarrieredTable(this).remove(cx, k); 947 } 948 return true; 949 } 950 951 bool MapObject::delete_impl(JSContext* cx, const CallArgs& args) { 952 // MapObject::trace does not trace deleted entries. Incremental GC therefore 953 // requires that no HeapPtr<Value> objects pointing to heap values be left 954 // alive in the hash table. 955 // 956 // OrderedHashMapImpl::remove() doesn't destroy the removed entry. It merely 957 // calls OrderedHashMapImpl::MapOps::makeEmpty. But that is sufficient, 958 // because makeEmpty clears the value by doing e->value = Value(), and in the 959 // case of Table, Value() means HeapPtr<Value>(), which is the same as 960 // HeapPtr<Value>(UndefinedValue()). 961 962 auto* mapObj = &args.thisv().toObject().as<MapObject>(); 963 bool found; 964 if (!mapObj->delete_(cx, args.get(0), &found)) { 965 return false; 966 } 967 args.rval().setBoolean(found); 968 return true; 969 } 970 971 bool MapObject::delete_(JSContext* cx, unsigned argc, Value* vp) { 972 AutoJSMethodProfilerEntry pseudoFrame(cx, "Map.prototype", "delete"); 973 CallArgs args = CallArgsFromVp(argc, vp); 974 return CallNonGenericMethod<MapObject::is, MapObject::delete_impl>(cx, args); 975 } 976 977 bool MapObject::iterator(JSContext* cx, IteratorKind kind, 978 Handle<MapObject*> obj, MutableHandleValue iter) { 979 JSObject* iterobj = MapIteratorObject::create(cx, obj, kind); 980 if (!iterobj) { 981 return false; 982 } 983 iter.setObject(*iterobj); 984 return true; 985 } 986 987 bool MapObject::iterator_impl(JSContext* cx, const CallArgs& args, 988 IteratorKind kind) { 989 Rooted<MapObject*> mapObj(cx, &args.thisv().toObject().as<MapObject>()); 990 return iterator(cx, kind, mapObj, args.rval()); 991 } 992 993 bool MapObject::keys_impl(JSContext* cx, const CallArgs& args) { 994 return iterator_impl(cx, args, IteratorKind::Keys); 995 } 996 997 bool MapObject::keys(JSContext* cx, unsigned argc, Value* vp) { 998 AutoJSMethodProfilerEntry pseudoFrame(cx, "Map.prototype", "keys"); 999 CallArgs args = CallArgsFromVp(argc, vp); 1000 return CallNonGenericMethod(cx, is, keys_impl, args); 1001 } 1002 1003 bool MapObject::values_impl(JSContext* cx, const CallArgs& args) { 1004 return iterator_impl(cx, args, IteratorKind::Values); 1005 } 1006 1007 bool MapObject::values(JSContext* cx, unsigned argc, Value* vp) { 1008 AutoJSMethodProfilerEntry pseudoFrame(cx, "Map.prototype", "values"); 1009 CallArgs args = CallArgsFromVp(argc, vp); 1010 return CallNonGenericMethod(cx, is, values_impl, args); 1011 } 1012 1013 bool MapObject::entries_impl(JSContext* cx, const CallArgs& args) { 1014 return iterator_impl(cx, args, IteratorKind::Entries); 1015 } 1016 1017 bool MapObject::entries(JSContext* cx, unsigned argc, Value* vp) { 1018 AutoJSMethodProfilerEntry pseudoFrame(cx, "Map.prototype", "entries"); 1019 CallArgs args = CallArgsFromVp(argc, vp); 1020 return CallNonGenericMethod(cx, is, entries_impl, args); 1021 } 1022 1023 bool MapObject::clear_impl(JSContext* cx, const CallArgs& args) { 1024 auto* mapObj = &args.thisv().toObject().as<MapObject>(); 1025 mapObj->clear(cx); 1026 args.rval().setUndefined(); 1027 return true; 1028 } 1029 1030 bool MapObject::clear(JSContext* cx, unsigned argc, Value* vp) { 1031 AutoJSMethodProfilerEntry pseudoFrame(cx, "Map.prototype", "clear"); 1032 CallArgs args = CallArgsFromVp(argc, vp); 1033 return CallNonGenericMethod(cx, is, clear_impl, args); 1034 } 1035 1036 void MapObject::clear(JSContext* cx) { 1037 if (isTenured()) { 1038 Table(this).clear(cx); 1039 } else { 1040 PreBarrieredTable(this).clear(cx); 1041 } 1042 } 1043 1044 /*** SetIterator ************************************************************/ 1045 1046 static const JSClassOps SetIteratorObjectClassOps = { 1047 nullptr, // addProperty 1048 nullptr, // delProperty 1049 nullptr, // enumerate 1050 nullptr, // newEnumerate 1051 nullptr, // resolve 1052 nullptr, // mayResolve 1053 SetIteratorObject::finalize, // finalize 1054 nullptr, // call 1055 nullptr, // construct 1056 nullptr, // trace 1057 }; 1058 1059 static const ClassExtension SetIteratorObjectClassExtension = { 1060 SetIteratorObject::objectMoved, // objectMovedOp 1061 }; 1062 1063 const JSClass SetIteratorObject::class_ = { 1064 "Set Iterator", 1065 JSCLASS_HAS_RESERVED_SLOTS(SetIteratorObject::SlotCount) | 1066 JSCLASS_FOREGROUND_FINALIZE | JSCLASS_SKIP_NURSERY_FINALIZE, 1067 &SetIteratorObjectClassOps, 1068 JS_NULL_CLASS_SPEC, 1069 &SetIteratorObjectClassExtension, 1070 }; 1071 1072 const JSFunctionSpec SetIteratorObject::methods[] = { 1073 JS_SELF_HOSTED_FN("next", "SetIteratorNext", 0, 0), 1074 JS_FS_END, 1075 }; 1076 1077 /* static */ 1078 bool GlobalObject::initSetIteratorProto(JSContext* cx, 1079 Handle<GlobalObject*> global) { 1080 Rooted<JSObject*> base( 1081 cx, GlobalObject::getOrCreateIteratorPrototype(cx, global)); 1082 if (!base) { 1083 return false; 1084 } 1085 Rooted<PlainObject*> proto( 1086 cx, GlobalObject::createBlankPrototypeInheriting<PlainObject>(cx, base)); 1087 if (!proto) { 1088 return false; 1089 } 1090 if (!JS_DefineFunctions(cx, proto, SetIteratorObject::methods) || 1091 !DefineToStringTag(cx, proto, cx->names().Set_Iterator_)) { 1092 return false; 1093 } 1094 if (!JSObject::setHasRealmFuseProperty(cx, proto)) { 1095 return false; 1096 } 1097 global->initBuiltinProto(ProtoKind::SetIteratorProto, proto); 1098 return true; 1099 } 1100 1101 SetIteratorObject* SetIteratorObject::create(JSContext* cx, 1102 Handle<SetObject*> setobj, 1103 Kind kind) { 1104 MOZ_ASSERT(kind != Kind::Keys); 1105 1106 Rooted<GlobalObject*> global(cx, &setobj->global()); 1107 Rooted<JSObject*> proto( 1108 cx, GlobalObject::getOrCreateSetIteratorPrototype(cx, global)); 1109 if (!proto) { 1110 return nullptr; 1111 } 1112 1113 SetIteratorObject* iterobj = 1114 NewObjectWithGivenProto<SetIteratorObject>(cx, proto); 1115 if (!iterobj) { 1116 return nullptr; 1117 } 1118 1119 if (IsInsideNursery(iterobj) && 1120 !HasRegisteredNurseryIterators(setobj.get())) { 1121 if (!cx->nursery().addSetWithNurseryIterators(setobj)) { 1122 ReportOutOfMemory(cx); 1123 return nullptr; 1124 } 1125 SetRegisteredNurseryIterators(setobj.get(), true); 1126 } 1127 1128 SetObject::Table(setobj).initIterator(iterobj, kind); 1129 1130 return iterobj; 1131 } 1132 1133 void SetIteratorObject::finalize(JS::GCContext* gcx, JSObject* obj) { 1134 MOZ_ASSERT(gcx->onMainThread()); 1135 MOZ_ASSERT(!IsInsideNursery(obj)); 1136 if (obj->as<SetIteratorObject>().isActive()) { 1137 obj->as<SetIteratorObject>().unlink(); 1138 } 1139 } 1140 1141 size_t SetIteratorObject::objectMoved(JSObject* obj, JSObject* old) { 1142 SetIteratorObject* iter = &obj->as<SetIteratorObject>(); 1143 if (!iter->isActive()) { 1144 return 0; 1145 } 1146 if (IsInsideNursery(old)) { 1147 SetObject* setObj = iter->target(); 1148 SetObject::Table(setObj).relinkNurseryIterator(iter); 1149 } else { 1150 iter->updateListAfterMove(&old->as<SetIteratorObject>()); 1151 } 1152 return 0; 1153 } 1154 1155 SetObject* SetIteratorObject::target() const { 1156 MOZ_ASSERT(isActive(), "only active iterators have a target object"); 1157 Value value = getFixedSlot(TargetSlot); 1158 return &MaybeForwarded(&value.toObject())->as<SetObject>(); 1159 } 1160 1161 bool SetIteratorObject::next(SetIteratorObject* setIterator, 1162 ArrayObject* resultObj) { 1163 // IC code calls this directly. 1164 AutoUnsafeCallWithABI unsafe; 1165 1166 // Check invariants for inlined _GetNextSetEntryForIterator. 1167 1168 // The array should be tenured, so that post-barrier can be done simply. 1169 MOZ_ASSERT(resultObj->isTenured()); 1170 1171 // The array elements should be fixed. 1172 MOZ_ASSERT(resultObj->hasFixedElements()); 1173 MOZ_ASSERT(resultObj->getDenseInitializedLength() == 1); 1174 MOZ_ASSERT(resultObj->getDenseCapacity() >= 1); 1175 1176 if (!setIterator->isActive()) { 1177 // Already done. 1178 return true; 1179 } 1180 1181 auto storeResult = [resultObj](Kind kind, const auto& element) { 1182 resultObj->setDenseElement(0, element.get()); 1183 }; 1184 SetObject* setObj = setIterator->target(); 1185 return SetObject::Table(setObj).iteratorNext(setIterator, storeResult); 1186 } 1187 1188 /* static */ 1189 JSObject* SetIteratorObject::createResult(JSContext* cx) { 1190 ArrayObject* resultObj = NewDenseFullyAllocatedArray(cx, 1, TenuredObject); 1191 if (!resultObj) { 1192 return nullptr; 1193 } 1194 1195 resultObj->setDenseInitializedLength(1); 1196 resultObj->initDenseElement(0, NullValue()); 1197 1198 return resultObj; 1199 } 1200 1201 /*** Set ********************************************************************/ 1202 1203 const JSClassOps SetObject::classOps_ = { 1204 nullptr, // addProperty 1205 nullptr, // delProperty 1206 nullptr, // enumerate 1207 nullptr, // newEnumerate 1208 nullptr, // resolve 1209 nullptr, // mayResolve 1210 nullptr, // finalize 1211 nullptr, // call 1212 nullptr, // construct 1213 trace, // trace 1214 }; 1215 1216 const ClassSpec SetObject::classSpec_ = { 1217 GenericCreateConstructor<SetObject::construct, 0, gc::AllocKind::FUNCTION, 1218 &jit::JitInfo_SetConstructor>, 1219 GenericCreatePrototype<SetObject>, 1220 nullptr, 1221 SetObject::staticProperties, 1222 SetObject::methods, 1223 SetObject::properties, 1224 SetObject::finishInit, 1225 }; 1226 1227 const ClassExtension SetObject::classExtension_ = { 1228 SetObject::objectMoved, // objectMovedOp 1229 }; 1230 1231 const JSClass SetObject::class_ = { 1232 "Set", 1233 JSCLASS_DELAY_METADATA_BUILDER | 1234 JSCLASS_HAS_RESERVED_SLOTS(SetObject::SlotCount) | 1235 JSCLASS_HAS_CACHED_PROTO(JSProto_Set), 1236 &SetObject::classOps_, &SetObject::classSpec_, &SetObject::classExtension_}; 1237 1238 const JSClass SetObject::protoClass_ = { 1239 "Set.prototype", 1240 JSCLASS_HAS_CACHED_PROTO(JSProto_Set), 1241 JS_NULL_CLASS_OPS, 1242 &SetObject::classSpec_, 1243 }; 1244 1245 const JSPropertySpec SetObject::properties[] = { 1246 JS_INLINABLE_PSG("size", size, 0, SetSize), 1247 JS_STRING_SYM_PS(toStringTag, "Set", JSPROP_READONLY), 1248 JS_PS_END, 1249 }; 1250 1251 const JSFunctionSpec SetObject::methods[] = { 1252 JS_INLINABLE_FN("has", has, 1, 0, SetHas), 1253 JS_INLINABLE_FN("add", add, 1, 0, SetAdd), 1254 JS_INLINABLE_FN("delete", delete_, 1, 0, SetDelete), 1255 JS_FN("entries", entries, 0, 0), 1256 JS_FN("clear", clear, 0, 0), 1257 JS_SELF_HOSTED_FN("forEach", "SetForEach", 2, 0), 1258 JS_SELF_HOSTED_FN("union", "SetUnion", 1, 0), 1259 JS_SELF_HOSTED_FN("difference", "SetDifference", 1, 0), 1260 JS_SELF_HOSTED_FN("intersection", "SetIntersection", 1, 0), 1261 JS_SELF_HOSTED_FN("symmetricDifference", "SetSymmetricDifference", 1, 0), 1262 JS_SELF_HOSTED_FN("isSubsetOf", "SetIsSubsetOf", 1, 0), 1263 JS_SELF_HOSTED_FN("isSupersetOf", "SetIsSupersetOf", 1, 0), 1264 JS_SELF_HOSTED_FN("isDisjointFrom", "SetIsDisjointFrom", 1, 0), 1265 JS_FN("values", values, 0, 0), 1266 // @@iterator and |keys| re-defined in finishInit so that they have the 1267 // same identity as |values|. 1268 JS_FN("keys", values, 0, 0), 1269 JS_SYM_FN(iterator, values, 0, 0), 1270 JS_FS_END, 1271 }; 1272 // clang-format on 1273 1274 const JSPropertySpec SetObject::staticProperties[] = { 1275 JS_SELF_HOSTED_SYM_GET(species, "$SetSpecies", 0), 1276 JS_PS_END, 1277 }; 1278 1279 /* static */ bool SetObject::finishInit(JSContext* cx, HandleObject ctor, 1280 HandleObject proto) { 1281 Handle<NativeObject*> nativeProto = proto.as<NativeObject>(); 1282 1283 RootedValue valuesFn(cx); 1284 RootedId valuesId(cx, NameToId(cx->names().values)); 1285 if (!NativeGetProperty(cx, nativeProto, valuesId, &valuesFn)) { 1286 return false; 1287 } 1288 1289 // 23.2.3.8 Set.prototype.keys() 1290 // The initial value of the "keys" property is the same function object 1291 // as the initial value of the "values" property. 1292 RootedId keysId(cx, NameToId(cx->names().keys)); 1293 if (!NativeDefineDataProperty(cx, nativeProto, keysId, valuesFn, 0)) { 1294 return false; 1295 } 1296 1297 // 23.2.3.11 Set.prototype[@@iterator]() 1298 // See above. 1299 RootedId iteratorId(cx, PropertyKey::Symbol(cx->wellKnownSymbols().iterator)); 1300 if (!NativeDefineDataProperty(cx, nativeProto, iteratorId, valuesFn, 0)) { 1301 return false; 1302 } 1303 1304 return JSObject::setHasRealmFuseProperty(cx, nativeProto); 1305 } 1306 1307 bool SetObject::keys(JS::MutableHandle<GCVector<JS::Value>> keys) { 1308 auto appendEntry = [&keys](auto& entry) { return keys.append(entry.get()); }; 1309 return Table(this).forEachEntry(appendEntry); 1310 } 1311 1312 bool SetObject::add(JSContext* cx, const Value& key) { 1313 HashableValue k; 1314 if (!k.setValue(cx, key)) { 1315 return false; 1316 } 1317 return addHashableValue(cx, k); 1318 } 1319 1320 bool SetObject::addHashableValue(JSContext* cx, const HashableValue& value) { 1321 bool needsPostBarriers = isTenured(); 1322 if (needsPostBarriers && !PostWriteBarrier(this, value)) { 1323 ReportOutOfMemory(cx); 1324 return false; 1325 } 1326 return Table(this).put(cx, value); 1327 } 1328 1329 SetObject* SetObject::createWithProto(JSContext* cx, HandleObject proto, 1330 NewObjectKind newKind) { 1331 MOZ_ASSERT(proto); 1332 1333 gc::AllocKind allocKind = gc::GetGCObjectKind(SlotCount); 1334 1335 AutoSetNewObjectMetadata metadata(cx); 1336 auto* setObj = 1337 NewObjectWithGivenProtoAndKinds<SetObject>(cx, proto, allocKind, newKind); 1338 if (!setObj) { 1339 return nullptr; 1340 } 1341 1342 UnbarrieredTable(setObj).initSlots(); 1343 setObj->initReservedSlot(NurseryKeysSlot, PrivateValue(nullptr)); 1344 setObj->initReservedSlot(RegisteredNurseryIteratorsSlot, BooleanValue(false)); 1345 return setObj; 1346 } 1347 1348 SetObject* SetObject::create(JSContext* cx, 1349 HandleObject proto /* = nullptr */) { 1350 if (proto) { 1351 return createWithProto(cx, proto, GenericObject); 1352 } 1353 1354 // This is the common case so use the template object's shape to optimize the 1355 // allocation. 1356 SetObject* templateObj = GlobalObject::getOrCreateSetTemplateObject(cx); 1357 if (!templateObj) { 1358 return nullptr; 1359 } 1360 1361 gc::AllocKind allocKind = templateObj->asTenured().getAllocKind(); 1362 MOZ_ASSERT(gc::GetGCKindSlots(allocKind) >= SlotCount); 1363 MOZ_ASSERT(!gc::IsFinalizedKind(allocKind)); 1364 1365 AutoSetNewObjectMetadata metadata(cx); 1366 Rooted<SharedShape*> shape(cx, templateObj->sharedShape()); 1367 auto* setObj = 1368 NativeObject::create<SetObject>(cx, allocKind, gc::Heap::Default, shape); 1369 if (!setObj) { 1370 return nullptr; 1371 } 1372 1373 UnbarrieredTable(setObj).initSlots(); 1374 setObj->initReservedSlot(NurseryKeysSlot, PrivateValue(nullptr)); 1375 setObj->initReservedSlot(RegisteredNurseryIteratorsSlot, BooleanValue(false)); 1376 return setObj; 1377 } 1378 1379 // static 1380 SetObject* GlobalObject::getOrCreateSetTemplateObject(JSContext* cx) { 1381 GlobalObjectData& data = cx->global()->data(); 1382 if (SetObject* obj = data.setObjectTemplate) { 1383 return obj; 1384 } 1385 1386 Rooted<JSObject*> proto(cx, 1387 GlobalObject::getOrCreatePrototype(cx, JSProto_Set)); 1388 if (!proto) { 1389 return nullptr; 1390 } 1391 auto* setObj = SetObject::createWithProto(cx, proto, TenuredObject); 1392 if (!setObj) { 1393 return nullptr; 1394 } 1395 1396 data.setObjectTemplate.init(setObj); 1397 return setObj; 1398 } 1399 1400 void SetObject::trace(JSTracer* trc, JSObject* obj) { 1401 SetObject* setobj = static_cast<SetObject*>(obj); 1402 Table(setobj).trace(trc); 1403 } 1404 1405 size_t SetObject::sizeOfData(mozilla::MallocSizeOf mallocSizeOf) { 1406 size_t size = 0; 1407 size += Table(this).sizeOfExcludingObject(mallocSizeOf); 1408 if (NurseryKeysVector* nurseryKeys = GetNurseryKeys(this)) { 1409 size += nurseryKeys->sizeOfIncludingThis(mallocSizeOf); 1410 } 1411 return size; 1412 } 1413 1414 size_t SetObject::objectMoved(JSObject* obj, JSObject* old) { 1415 auto* setObj = &obj->as<SetObject>(); 1416 1417 Table(setObj).updateIteratorsAfterMove(&old->as<SetObject>()); 1418 1419 if (IsInsideNursery(old)) { 1420 Nursery& nursery = setObj->runtimeFromMainThread()->gc.nursery(); 1421 Table(setObj).maybeMoveBufferOnPromotion(nursery); 1422 } 1423 1424 return 0; 1425 } 1426 1427 void SetObject::clearNurseryIteratorsBeforeMinorGC() { 1428 Table(this).clearNurseryIterators(); 1429 } 1430 1431 /* static */ 1432 SetObject* SetObject::sweepAfterMinorGC(JS::GCContext* gcx, SetObject* setobj) { 1433 Nursery& nursery = gcx->runtime()->gc.nursery(); 1434 bool wasInCollectedRegion = nursery.inCollectedRegion(setobj); 1435 if (wasInCollectedRegion && !IsForwarded(setobj)) { 1436 // This SetObject is dead. 1437 return nullptr; 1438 } 1439 1440 setobj = MaybeForwarded(setobj); 1441 1442 // Keep |setobj| registered with the nursery if it still has nursery 1443 // iterators. 1444 bool hasNurseryIterators = Table(setobj).hasNurseryIterators(); 1445 SetRegisteredNurseryIterators(setobj, hasNurseryIterators); 1446 return hasNurseryIterators ? setobj : nullptr; 1447 } 1448 1449 bool SetObject::tryOptimizeCtorWithIterable(JSContext* cx, 1450 const Value& iterableVal, 1451 bool* optimized) { 1452 MOZ_ASSERT(!iterableVal.isNullOrUndefined()); 1453 MOZ_ASSERT(!*optimized); 1454 1455 if (!CanOptimizeMapOrSetCtorWithIterable<JSProto_Set>(SetObject::add, this, 1456 cx)) { 1457 return true; 1458 } 1459 1460 if (!iterableVal.isObject()) { 1461 return true; 1462 } 1463 JSObject* iterable = &iterableVal.toObject(); 1464 1465 // Fast path for `new Set(array)`. 1466 if (IsOptimizableArrayForMapOrSetCtor<MapOrSet::Set>(iterable, cx)) { 1467 ArrayObject* array = &iterable->as<ArrayObject>(); 1468 uint32_t len = array->getDenseInitializedLength(); 1469 1470 for (uint32_t index = 0; index < len; index++) { 1471 Value keyVal = array->getDenseElement(index); 1472 MOZ_ASSERT(!keyVal.isMagic(JS_ELEMENTS_HOLE)); 1473 if (!add(cx, keyVal)) { 1474 return false; 1475 } 1476 } 1477 1478 *optimized = true; 1479 return true; 1480 } 1481 1482 // Fast path for `new Set(set)`. 1483 if (IsSetObjectWithDefaultIterator(iterable, cx)) { 1484 auto* iterableSet = &iterable->as<SetObject>(); 1485 if (!IsSetObjectWithDefaultIterator(iterableSet, cx)) { 1486 return true; 1487 } 1488 auto addEntry = [cx, this](auto& entry) { 1489 return addHashableValue(cx, entry); 1490 }; 1491 if (!Table(iterableSet).forEachEntry(addEntry)) { 1492 return false; 1493 } 1494 *optimized = true; 1495 return true; 1496 } 1497 1498 return true; 1499 } 1500 1501 // static 1502 SetObject* SetObject::createFromIterable(JSContext* cx, Handle<JSObject*> proto, 1503 Handle<Value> iterable, 1504 Handle<SetObject*> allocatedFromJit) { 1505 // A null-or-undefined |iterable| is quite common and we check for this in JIT 1506 // code. 1507 MOZ_ASSERT_IF(allocatedFromJit, !iterable.isNullOrUndefined()); 1508 1509 Rooted<SetObject*> obj(cx, allocatedFromJit); 1510 if (!obj) { 1511 obj = SetObject::create(cx, proto); 1512 if (!obj) { 1513 return nullptr; 1514 } 1515 } 1516 1517 if (!iterable.isNullOrUndefined()) { 1518 bool optimized = false; 1519 if (!obj->tryOptimizeCtorWithIterable(cx, iterable, &optimized)) { 1520 return nullptr; 1521 } 1522 if (!optimized) { 1523 FixedInvokeArgs<1> args(cx); 1524 args[0].set(iterable); 1525 1526 RootedValue thisv(cx, ObjectValue(*obj)); 1527 if (!CallSelfHostedFunction(cx, cx->names().SetConstructorInit, thisv, 1528 args, args.rval())) { 1529 return nullptr; 1530 } 1531 } 1532 } 1533 1534 return obj; 1535 } 1536 1537 bool SetObject::construct(JSContext* cx, unsigned argc, Value* vp) { 1538 AutoJSConstructorProfilerEntry pseudoFrame(cx, "Set"); 1539 CallArgs args = CallArgsFromVp(argc, vp); 1540 1541 if (!ThrowIfNotConstructing(cx, args, "Set")) { 1542 return false; 1543 } 1544 1545 RootedObject proto(cx); 1546 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Set, &proto)) { 1547 return false; 1548 } 1549 1550 SetObject* obj = SetObject::createFromIterable(cx, proto, args.get(0)); 1551 if (!obj) { 1552 return false; 1553 } 1554 1555 args.rval().setObject(*obj); 1556 return true; 1557 } 1558 1559 bool SetObject::is(HandleValue v) { 1560 return v.isObject() && v.toObject().hasClass(&class_); 1561 } 1562 1563 bool SetObject::is(HandleObject o) { return o->hasClass(&class_); } 1564 1565 uint32_t SetObject::size() { 1566 static_assert(sizeof(Table(this).count()) <= sizeof(uint32_t), 1567 "set count must be precisely representable as a JS number"); 1568 return Table(this).count(); 1569 } 1570 1571 bool SetObject::size_impl(JSContext* cx, const CallArgs& args) { 1572 auto* setObj = &args.thisv().toObject().as<SetObject>(); 1573 args.rval().setNumber(setObj->size()); 1574 return true; 1575 } 1576 1577 bool SetObject::size(JSContext* cx, unsigned argc, Value* vp) { 1578 AutoJSMethodProfilerEntry pseudoFrame(cx, "Set.prototype", "size"); 1579 CallArgs args = CallArgsFromVp(argc, vp); 1580 return CallNonGenericMethod<SetObject::is, SetObject::size_impl>(cx, args); 1581 } 1582 1583 bool SetObject::has_impl(JSContext* cx, const CallArgs& args) { 1584 auto* setObj = &args.thisv().toObject().as<SetObject>(); 1585 bool found; 1586 if (!setObj->has(cx, args.get(0), &found)) { 1587 return false; 1588 } 1589 args.rval().setBoolean(found); 1590 return true; 1591 } 1592 1593 bool SetObject::has(JSContext* cx, const Value& key, bool* rval) { 1594 HashableValue k; 1595 if (!k.setValue(cx, key)) { 1596 return false; 1597 } 1598 1599 *rval = Table(this).has(k); 1600 return true; 1601 } 1602 1603 bool SetObject::has(JSContext* cx, unsigned argc, Value* vp) { 1604 AutoJSMethodProfilerEntry pseudoFrame(cx, "Set.prototype", "has"); 1605 CallArgs args = CallArgsFromVp(argc, vp); 1606 return CallNonGenericMethod<SetObject::is, SetObject::has_impl>(cx, args); 1607 } 1608 1609 bool SetObject::add_impl(JSContext* cx, const CallArgs& args) { 1610 auto* setObj = &args.thisv().toObject().as<SetObject>(); 1611 if (!setObj->add(cx, args.get(0))) { 1612 return false; 1613 } 1614 args.rval().set(args.thisv()); 1615 return true; 1616 } 1617 1618 bool SetObject::add(JSContext* cx, unsigned argc, Value* vp) { 1619 AutoJSMethodProfilerEntry pseudoFrame(cx, "Set.prototype", "add"); 1620 CallArgs args = CallArgsFromVp(argc, vp); 1621 return CallNonGenericMethod<SetObject::is, SetObject::add_impl>(cx, args); 1622 } 1623 1624 bool SetObject::delete_(JSContext* cx, const Value& key, bool* rval) { 1625 HashableValue k; 1626 if (!k.setValue(cx, key)) { 1627 return false; 1628 } 1629 1630 *rval = Table(this).remove(cx, k); 1631 return true; 1632 } 1633 1634 bool SetObject::delete_impl(JSContext* cx, const CallArgs& args) { 1635 auto* setObj = &args.thisv().toObject().as<SetObject>(); 1636 bool found; 1637 if (!setObj->delete_(cx, args.get(0), &found)) { 1638 return false; 1639 } 1640 args.rval().setBoolean(found); 1641 return true; 1642 } 1643 1644 bool SetObject::delete_(JSContext* cx, unsigned argc, Value* vp) { 1645 AutoJSMethodProfilerEntry pseudoFrame(cx, "Set.prototype", "delete"); 1646 CallArgs args = CallArgsFromVp(argc, vp); 1647 return CallNonGenericMethod<SetObject::is, SetObject::delete_impl>(cx, args); 1648 } 1649 1650 bool SetObject::iterator(JSContext* cx, IteratorKind kind, 1651 Handle<SetObject*> obj, MutableHandleValue iter) { 1652 JSObject* iterobj = SetIteratorObject::create(cx, obj, kind); 1653 if (!iterobj) { 1654 return false; 1655 } 1656 iter.setObject(*iterobj); 1657 return true; 1658 } 1659 1660 bool SetObject::iterator_impl(JSContext* cx, const CallArgs& args, 1661 IteratorKind kind) { 1662 Rooted<SetObject*> setObj(cx, &args.thisv().toObject().as<SetObject>()); 1663 return iterator(cx, kind, setObj, args.rval()); 1664 } 1665 1666 bool SetObject::values_impl(JSContext* cx, const CallArgs& args) { 1667 return iterator_impl(cx, args, IteratorKind::Values); 1668 } 1669 1670 bool SetObject::values(JSContext* cx, unsigned argc, Value* vp) { 1671 AutoJSMethodProfilerEntry pseudoFrame(cx, "Set.prototype", "values"); 1672 CallArgs args = CallArgsFromVp(argc, vp); 1673 return CallNonGenericMethod(cx, is, values_impl, args); 1674 } 1675 1676 bool SetObject::entries_impl(JSContext* cx, const CallArgs& args) { 1677 return iterator_impl(cx, args, IteratorKind::Entries); 1678 } 1679 1680 bool SetObject::entries(JSContext* cx, unsigned argc, Value* vp) { 1681 AutoJSMethodProfilerEntry pseudoFrame(cx, "Set.prototype", "entries"); 1682 CallArgs args = CallArgsFromVp(argc, vp); 1683 return CallNonGenericMethod(cx, is, entries_impl, args); 1684 } 1685 1686 void SetObject::clear(JSContext* cx) { Table(this).clear(cx); } 1687 1688 bool SetObject::clear_impl(JSContext* cx, const CallArgs& args) { 1689 auto* setObj = &args.thisv().toObject().as<SetObject>(); 1690 setObj->clear(cx); 1691 args.rval().setUndefined(); 1692 return true; 1693 } 1694 1695 bool SetObject::clear(JSContext* cx, unsigned argc, Value* vp) { 1696 AutoJSMethodProfilerEntry pseudoFrame(cx, "Set.prototype", "clear"); 1697 CallArgs args = CallArgsFromVp(argc, vp); 1698 return CallNonGenericMethod(cx, is, clear_impl, args); 1699 } 1700 1701 bool SetObject::copy(JSContext* cx, unsigned argc, Value* vp) { 1702 CallArgs args = CallArgsFromVp(argc, vp); 1703 MOZ_ASSERT(args.length() == 1); 1704 MOZ_ASSERT(SetObject::is(args[0])); 1705 1706 auto* result = SetObject::create(cx); 1707 if (!result) { 1708 return false; 1709 } 1710 1711 auto* from = &args[0].toObject().as<SetObject>(); 1712 1713 auto addToResult = [cx, result](auto& entry) { 1714 return result->addHashableValue(cx, entry); 1715 }; 1716 if (!Table(from).forEachEntry(addToResult)) { 1717 return false; 1718 } 1719 1720 args.rval().setObject(*result); 1721 return true; 1722 } 1723 1724 /*** JS static utility functions ********************************************/ 1725 1726 static bool forEach(const char* funcName, JSContext* cx, HandleObject obj, 1727 HandleValue callbackFn, HandleValue thisArg) { 1728 CHECK_THREAD(cx); 1729 1730 RootedId forEachId(cx, NameToId(cx->names().forEach)); 1731 RootedFunction forEachFunc( 1732 cx, JS::GetSelfHostedFunction(cx, funcName, forEachId, 2)); 1733 if (!forEachFunc) { 1734 return false; 1735 } 1736 1737 RootedValue fval(cx, ObjectValue(*forEachFunc)); 1738 return Call(cx, fval, obj, callbackFn, thisArg, &fval); 1739 } 1740 1741 // RAII class that unwraps a wrapped Map or Set object and then enters its 1742 // realm. 1743 template <typename TableObject> 1744 class MOZ_RAII AutoEnterTableRealm { 1745 mozilla::Maybe<AutoRealm> ar_; 1746 Rooted<TableObject*> unwrapped_; 1747 1748 public: 1749 AutoEnterTableRealm(JSContext* cx, JSObject* obj) : unwrapped_(cx) { 1750 JSObject* unwrapped = UncheckedUnwrap(obj); 1751 MOZ_ASSERT(unwrapped != obj); 1752 MOZ_RELEASE_ASSERT(unwrapped->is<TableObject>()); 1753 unwrapped_ = &unwrapped->as<TableObject>(); 1754 ar_.emplace(cx, unwrapped_); 1755 } 1756 Handle<TableObject*> unwrapped() const { return unwrapped_; } 1757 }; 1758 1759 /*** JS public APIs *********************************************************/ 1760 1761 JS_PUBLIC_API JSObject* JS::NewMapObject(JSContext* cx) { 1762 return MapObject::create(cx); 1763 } 1764 1765 JS_PUBLIC_API uint32_t JS::MapSize(JSContext* cx, HandleObject obj) { 1766 CHECK_THREAD(cx); 1767 cx->check(obj); 1768 1769 if (obj->is<MapObject>()) { 1770 return obj.as<MapObject>()->size(); 1771 } 1772 1773 AutoEnterTableRealm<MapObject> enter(cx, obj); 1774 return enter.unwrapped()->size(); 1775 } 1776 1777 JS_PUBLIC_API bool JS::MapGet(JSContext* cx, HandleObject obj, HandleValue key, 1778 MutableHandleValue rval) { 1779 CHECK_THREAD(cx); 1780 cx->check(obj, key, rval); 1781 1782 if (obj->is<MapObject>()) { 1783 return obj.as<MapObject>()->get(cx, key, rval); 1784 } 1785 1786 { 1787 AutoEnterTableRealm<MapObject> enter(cx, obj); 1788 Rooted<Value> wrappedKey(cx, key); 1789 if (!JS_WrapValue(cx, &wrappedKey)) { 1790 return false; 1791 } 1792 if (!enter.unwrapped()->get(cx, wrappedKey, rval)) { 1793 return false; 1794 } 1795 } 1796 return JS_WrapValue(cx, rval); 1797 } 1798 1799 JS_PUBLIC_API bool JS::MapSet(JSContext* cx, HandleObject obj, HandleValue key, 1800 HandleValue val) { 1801 CHECK_THREAD(cx); 1802 cx->check(obj, key, val); 1803 1804 if (obj->is<MapObject>()) { 1805 return obj.as<MapObject>()->set(cx, key, val); 1806 } 1807 1808 AutoEnterTableRealm<MapObject> enter(cx, obj); 1809 Rooted<Value> wrappedKey(cx, key); 1810 Rooted<Value> wrappedValue(cx, val); 1811 if (!JS_WrapValue(cx, &wrappedKey) || !JS_WrapValue(cx, &wrappedValue)) { 1812 return false; 1813 } 1814 return enter.unwrapped()->set(cx, wrappedKey, wrappedValue); 1815 } 1816 1817 JS_PUBLIC_API bool JS::MapHas(JSContext* cx, HandleObject obj, HandleValue key, 1818 bool* rval) { 1819 CHECK_THREAD(cx); 1820 cx->check(obj, key); 1821 1822 if (obj->is<MapObject>()) { 1823 return obj.as<MapObject>()->has(cx, key, rval); 1824 } 1825 1826 AutoEnterTableRealm<MapObject> enter(cx, obj); 1827 Rooted<Value> wrappedKey(cx, key); 1828 if (!JS_WrapValue(cx, &wrappedKey)) { 1829 return false; 1830 } 1831 return enter.unwrapped()->has(cx, wrappedKey, rval); 1832 } 1833 1834 JS_PUBLIC_API bool JS::MapGetOrInsert(JSContext* cx, HandleObject obj, 1835 HandleValue key, HandleValue val, 1836 MutableHandleValue rval) { 1837 CHECK_THREAD(cx); 1838 cx->check(obj, key, val); 1839 1840 if (obj->is<MapObject>()) { 1841 return obj.as<MapObject>()->getOrInsert(cx, key, val, rval); 1842 } 1843 { 1844 AutoEnterTableRealm<MapObject> enter(cx, obj); 1845 Rooted<Value> wrappedKey(cx, key); 1846 Rooted<Value> wrappedValue(cx, val); 1847 if (!JS_WrapValue(cx, &wrappedKey) || !JS_WrapValue(cx, &wrappedValue)) { 1848 return false; 1849 } 1850 if (!enter.unwrapped()->getOrInsert(cx, wrappedKey, wrappedValue, rval)) { 1851 return false; 1852 } 1853 } 1854 return JS_WrapValue(cx, rval); 1855 } 1856 1857 JS_PUBLIC_API bool JS::MapDelete(JSContext* cx, HandleObject obj, 1858 HandleValue key, bool* rval) { 1859 CHECK_THREAD(cx); 1860 cx->check(obj, key); 1861 1862 if (obj->is<MapObject>()) { 1863 return obj.as<MapObject>()->delete_(cx, key, rval); 1864 } 1865 1866 AutoEnterTableRealm<MapObject> enter(cx, obj); 1867 Rooted<Value> wrappedKey(cx, key); 1868 if (!JS_WrapValue(cx, &wrappedKey)) { 1869 return false; 1870 } 1871 return enter.unwrapped()->delete_(cx, wrappedKey, rval); 1872 } 1873 1874 JS_PUBLIC_API bool JS::MapClear(JSContext* cx, HandleObject obj) { 1875 CHECK_THREAD(cx); 1876 cx->check(obj); 1877 1878 if (obj->is<MapObject>()) { 1879 obj.as<MapObject>()->clear(cx); 1880 return true; 1881 } 1882 1883 AutoEnterTableRealm<MapObject> enter(cx, obj); 1884 enter.unwrapped()->clear(cx); 1885 return true; 1886 } 1887 1888 template <typename TableObject> 1889 [[nodiscard]] static bool CreateIterator( 1890 JSContext* cx, typename TableObject::IteratorKind kind, 1891 Handle<JSObject*> obj, MutableHandle<Value> rval) { 1892 CHECK_THREAD(cx); 1893 cx->check(obj); 1894 1895 if (obj->is<TableObject>()) { 1896 return TableObject::iterator(cx, kind, obj.as<TableObject>(), rval); 1897 } 1898 1899 { 1900 AutoEnterTableRealm<TableObject> enter(cx, obj); 1901 if (!TableObject::iterator(cx, kind, enter.unwrapped(), rval)) { 1902 return false; 1903 } 1904 } 1905 return JS_WrapValue(cx, rval); 1906 } 1907 1908 JS_PUBLIC_API bool JS::MapKeys(JSContext* cx, HandleObject obj, 1909 MutableHandleValue rval) { 1910 return CreateIterator<MapObject>(cx, MapObject::IteratorKind::Keys, obj, 1911 rval); 1912 } 1913 1914 JS_PUBLIC_API bool JS::MapValues(JSContext* cx, HandleObject obj, 1915 MutableHandleValue rval) { 1916 return CreateIterator<MapObject>(cx, MapObject::IteratorKind::Values, obj, 1917 rval); 1918 } 1919 1920 JS_PUBLIC_API bool JS::MapEntries(JSContext* cx, HandleObject obj, 1921 MutableHandleValue rval) { 1922 return CreateIterator<MapObject>(cx, MapObject::IteratorKind::Entries, obj, 1923 rval); 1924 } 1925 1926 JS_PUBLIC_API bool JS::MapForEach(JSContext* cx, HandleObject obj, 1927 HandleValue callbackFn, HandleValue thisVal) { 1928 return forEach("MapForEach", cx, obj, callbackFn, thisVal); 1929 } 1930 1931 JS_PUBLIC_API JSObject* JS::NewSetObject(JSContext* cx) { 1932 return SetObject::create(cx); 1933 } 1934 1935 JS_PUBLIC_API uint32_t JS::SetSize(JSContext* cx, HandleObject obj) { 1936 CHECK_THREAD(cx); 1937 cx->check(obj); 1938 1939 if (obj->is<SetObject>()) { 1940 return obj.as<SetObject>()->size(); 1941 } 1942 1943 AutoEnterTableRealm<SetObject> enter(cx, obj); 1944 return enter.unwrapped()->size(); 1945 } 1946 1947 JS_PUBLIC_API bool JS::SetAdd(JSContext* cx, HandleObject obj, 1948 HandleValue key) { 1949 CHECK_THREAD(cx); 1950 cx->check(obj, key); 1951 1952 if (obj->is<SetObject>()) { 1953 return obj.as<SetObject>()->add(cx, key); 1954 } 1955 1956 AutoEnterTableRealm<SetObject> enter(cx, obj); 1957 Rooted<Value> wrappedKey(cx, key); 1958 if (!JS_WrapValue(cx, &wrappedKey)) { 1959 return false; 1960 } 1961 return enter.unwrapped()->add(cx, wrappedKey); 1962 } 1963 1964 JS_PUBLIC_API bool JS::SetHas(JSContext* cx, HandleObject obj, HandleValue key, 1965 bool* rval) { 1966 CHECK_THREAD(cx); 1967 cx->check(obj, key); 1968 1969 if (obj->is<SetObject>()) { 1970 return obj.as<SetObject>()->has(cx, key, rval); 1971 } 1972 1973 AutoEnterTableRealm<SetObject> enter(cx, obj); 1974 Rooted<Value> wrappedKey(cx, key); 1975 if (!JS_WrapValue(cx, &wrappedKey)) { 1976 return false; 1977 } 1978 return enter.unwrapped()->has(cx, wrappedKey, rval); 1979 } 1980 1981 JS_PUBLIC_API bool JS::SetDelete(JSContext* cx, HandleObject obj, 1982 HandleValue key, bool* rval) { 1983 CHECK_THREAD(cx); 1984 cx->check(obj, key); 1985 1986 if (obj->is<SetObject>()) { 1987 return obj.as<SetObject>()->delete_(cx, key, rval); 1988 } 1989 1990 AutoEnterTableRealm<SetObject> enter(cx, obj); 1991 Rooted<Value> wrappedKey(cx, key); 1992 if (!JS_WrapValue(cx, &wrappedKey)) { 1993 return false; 1994 } 1995 return enter.unwrapped()->delete_(cx, wrappedKey, rval); 1996 } 1997 1998 JS_PUBLIC_API bool JS::SetClear(JSContext* cx, HandleObject obj) { 1999 CHECK_THREAD(cx); 2000 cx->check(obj); 2001 2002 if (obj->is<SetObject>()) { 2003 obj.as<SetObject>()->clear(cx); 2004 return true; 2005 } 2006 2007 AutoEnterTableRealm<SetObject> enter(cx, obj); 2008 enter.unwrapped()->clear(cx); 2009 return true; 2010 } 2011 2012 JS_PUBLIC_API bool JS::SetKeys(JSContext* cx, HandleObject obj, 2013 MutableHandleValue rval) { 2014 return SetValues(cx, obj, rval); 2015 } 2016 2017 JS_PUBLIC_API bool JS::SetValues(JSContext* cx, HandleObject obj, 2018 MutableHandleValue rval) { 2019 return CreateIterator<SetObject>(cx, SetObject::IteratorKind::Values, obj, 2020 rval); 2021 } 2022 2023 JS_PUBLIC_API bool JS::SetEntries(JSContext* cx, HandleObject obj, 2024 MutableHandleValue rval) { 2025 return CreateIterator<SetObject>(cx, SetObject::IteratorKind::Entries, obj, 2026 rval); 2027 } 2028 2029 JS_PUBLIC_API bool JS::SetForEach(JSContext* cx, HandleObject obj, 2030 HandleValue callbackFn, HandleValue thisVal) { 2031 return forEach("SetForEach", cx, obj, callbackFn, thisVal); 2032 } 2033 2034 JS_PUBLIC_API bool js::GetSetObjectKeys( 2035 JSContext* cx, JS::HandleObject obj, 2036 JS::MutableHandle<JS::GCVector<JS::Value>> keys) { 2037 CHECK_THREAD(cx); 2038 cx->check(obj); 2039 2040 if (obj->is<SetObject>()) { 2041 return obj->as<SetObject>().keys(keys); 2042 } 2043 2044 { 2045 AutoEnterTableRealm<SetObject> enter(cx, obj); 2046 if (!enter.unwrapped()->keys(keys)) { 2047 return false; 2048 } 2049 } 2050 2051 for (uint32_t i = 0; i < keys.length(); i++) { 2052 if (!JS_WrapValue(cx, keys[i])) { 2053 return false; 2054 } 2055 } 2056 2057 return true; 2058 } 2059 2060 JS_PUBLIC_API bool js::GetMapObjectKeysAndValuesInterleaved( 2061 JSContext* cx, JS::HandleObject obj, 2062 JS::MutableHandle<JS::GCVector<JS::Value>> entries) { 2063 CHECK_THREAD(cx); 2064 cx->check(obj); 2065 2066 if (obj->is<MapObject>()) { 2067 return obj->as<MapObject>().getKeysAndValuesInterleaved(entries); 2068 } 2069 2070 { 2071 AutoEnterTableRealm<MapObject> enter(cx, obj); 2072 if (!enter.unwrapped()->getKeysAndValuesInterleaved(entries)) { 2073 return false; 2074 } 2075 } 2076 2077 for (uint32_t i = 0; i < entries.length(); i++) { 2078 if (!JS_WrapValue(cx, entries[i])) { 2079 return false; 2080 } 2081 } 2082 2083 return true; 2084 }