NativeObject.h (72526B)
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 #ifndef vm_NativeObject_h 8 #define vm_NativeObject_h 9 10 #include "mozilla/Assertions.h" 11 #include "mozilla/Attributes.h" 12 #include "mozilla/Maybe.h" 13 14 #include <algorithm> 15 #include <stdint.h> 16 17 #include "NamespaceImports.h" 18 19 #include "gc/Barrier.h" 20 #include "gc/BufferAllocator.h" 21 #include "gc/MaybeRooted.h" 22 #include "gc/ZoneAllocator.h" 23 #include "js/shadow/Object.h" // JS::shadow::Object 24 #include "js/shadow/Zone.h" // JS::shadow::Zone 25 #include "js/Value.h" 26 #include "vm/GetterSetter.h" 27 #include "vm/JSAtomUtils.h" // AtomIsMarked 28 #include "vm/JSObject.h" 29 #include "vm/Shape.h" 30 #include "vm/StringType.h" 31 32 namespace js { 33 34 class JS_PUBLIC_API GenericPrinter; 35 class IteratorProperty; 36 class PropertyResult; 37 38 namespace gc { 39 class TenuringTracer; 40 } // namespace gc 41 42 /* 43 * To really poison a set of values, using 'magic' or 'undefined' isn't good 44 * enough since often these will just be ignored by buggy code (see bug 629974) 45 * in debug builds and crash in release builds. Instead, we use a safe-for-crash 46 * pointer. 47 */ 48 static MOZ_ALWAYS_INLINE void Debug_SetValueRangeToCrashOnTouch(Value* beg, 49 Value* end) { 50 #ifdef DEBUG 51 for (Value* v = beg; v != end; ++v) { 52 *v = js::PoisonedObjectValue(0x48); 53 } 54 #endif 55 } 56 57 static MOZ_ALWAYS_INLINE void Debug_SetValueRangeToCrashOnTouch(Value* vec, 58 size_t len) { 59 #ifdef DEBUG 60 Debug_SetValueRangeToCrashOnTouch(vec, vec + len); 61 #endif 62 } 63 64 static MOZ_ALWAYS_INLINE void Debug_SetValueRangeToCrashOnTouch( 65 GCPtr<Value>* vec, size_t len) { 66 #ifdef DEBUG 67 Debug_SetValueRangeToCrashOnTouch((Value*)vec, len); 68 #endif 69 } 70 71 static MOZ_ALWAYS_INLINE void Debug_SetSlotRangeToCrashOnTouch(HeapSlot* vec, 72 uint32_t len) { 73 #ifdef DEBUG 74 Debug_SetValueRangeToCrashOnTouch((Value*)vec, len); 75 #endif 76 } 77 78 static MOZ_ALWAYS_INLINE void Debug_SetSlotRangeToCrashOnTouch(HeapSlot* begin, 79 HeapSlot* end) { 80 #ifdef DEBUG 81 Debug_SetValueRangeToCrashOnTouch((Value*)begin, end - begin); 82 #endif 83 } 84 85 class ArrayObject; 86 87 /** 88 * 10.4.2.4 ArraySetLength ( A, Desc ) 89 * 90 * ES2025 draft rev ac21460fedf4b926520b06c9820bdbebad596a8b 91 * 92 * |id| must be "length", |desc| is the new non-accessor descriptor, and 93 * |result| receives an error code if the change is invalid. 94 */ 95 extern bool ArraySetLength(JSContext* cx, Handle<ArrayObject*> obj, HandleId id, 96 Handle<PropertyDescriptor> desc, 97 ObjectOpResult& result); 98 99 /* 100 * [SMDOC] NativeObject Elements layout 101 * 102 * Elements header used for native objects. The elements component of such 103 * objects offers an efficient representation for all or some of the indexed 104 * properties of the object, using a flat array of Values rather than a shape 105 * hierarchy stored in the object's slots. This structure is immediately 106 * followed by an array of elements, with the elements member in an object 107 * pointing to the beginning of that array (the end of this structure). See 108 * below for usage of this structure. 109 * 110 * The sets of properties represented by an object's elements and slots 111 * are disjoint. The elements contain only indexed properties, while the slots 112 * can contain both named and indexed properties; any indexes in the slots are 113 * distinct from those in the elements. If isIndexed() is false for an object, 114 * all indexed properties (if any) are stored in the dense elements. 115 * 116 * Indexes will be stored in the object's slots instead of its elements in 117 * the following case: 118 * - there are more than MIN_SPARSE_INDEX slots total and the load factor 119 * (COUNT / capacity) is less than 0.25 120 * - a property is defined that has non-default property attributes. 121 * 122 * We track these pieces of metadata for dense elements: 123 * - The length property as a uint32_t, accessible for array objects with 124 * ArrayObject::{length,setLength}(). This is unused for non-arrays. 125 * - The number of element slots (capacity), gettable with 126 * getDenseCapacity(). 127 * - The array's initialized length, accessible with 128 * getDenseInitializedLength(). 129 * 130 * Holes in the array are represented by MagicValue(JS_ELEMENTS_HOLE) values. 131 * These indicate indexes which are not dense properties of the array. The 132 * property may, however, be held by the object's properties. 133 * 134 * The capacity and length of an object's elements are almost entirely 135 * unrelated! In general the length may be greater than, less than, or equal 136 * to the capacity. The first case occurs with |new Array(100)|. The length 137 * is 100, but the capacity remains 0 (indices below length and above capacity 138 * must be treated as holes) until elements between capacity and length are 139 * set. The other two cases are common, depending upon the number of elements 140 * in an array and the underlying allocator used for element storage. 141 * 142 * The only case in which the capacity and length of an object's elements are 143 * related is when the object is an array with non-writable length. In this 144 * case the capacity is always less than or equal to the length. This permits 145 * JIT code to optimize away the check for non-writable length when assigning 146 * to possibly out-of-range elements: such code already has to check for 147 * |index < capacity|, and fallback code checks for non-writable length. 148 * 149 * The initialized length of an object specifies the number of elements that 150 * have been initialized. All elements above the initialized length are 151 * holes in the object, and the memory for all elements between the initialized 152 * length and capacity is left uninitialized. The initialized length is some 153 * value less than or equal to both the object's length and the object's 154 * capacity. 155 * 156 * There is flexibility in exactly the value the initialized length must hold, 157 * e.g. if an array has length 5, capacity 10, completely empty, it is valid 158 * for the initialized length to be any value between zero and 5, as long as 159 * the in memory values below the initialized length have been initialized with 160 * a hole value. However, in such cases we want to keep the initialized length 161 * as small as possible: if the object is known to have no hole values below 162 * its initialized length, then it is "packed" and can be accessed much faster 163 * by JIT code. 164 * 165 * Elements do not track property creation order, so enumerating the elements 166 * of an object does not necessarily visit indexes in the order they were 167 * created. 168 * 169 * 170 * [SMDOC] NativeObject shifted elements optimization 171 * 172 * Shifted elements 173 * ---------------- 174 * It's pretty common to use an array as a queue, like this: 175 * 176 * while (arr.length > 0) 177 * foo(arr.shift()); 178 * 179 * To ensure we don't get quadratic behavior on this, elements can be 'shifted' 180 * in memory. tryShiftDenseElements does this by incrementing elements_ to point 181 * to the next element and moving the ObjectElements header in memory (so it's 182 * stored where the shifted Value used to be). 183 * 184 * Shifted elements can be moved when we grow the array, when the array is 185 * made non-extensible (for simplicity, shifted elements are not supported on 186 * objects that are non-extensible, have copy-on-write elements, or on arrays 187 * with non-writable length). 188 */ 189 class ObjectElements { 190 public: 191 enum Flags : uint16_t { 192 // Elements are stored inline in the object allocation. 193 // An object allocated with the FIXED flag set can have the flag unset later 194 // if `growElements()` is called to increase the capacity beyond what was 195 // initially allocated. Once the flag is unset, it will remain so for the 196 // rest of the lifetime of the object. 197 FIXED = 0x1, 198 199 // Present only if these elements correspond to an array with 200 // non-writable length; never present for non-arrays. 201 NONWRITABLE_ARRAY_LENGTH = 0x2, 202 203 // For TypedArrays only: this TypedArray's storage is mapping shared 204 // memory. This is a static property of the TypedArray, set when it 205 // is created and never changed. 206 SHARED_MEMORY = 0x8, 207 208 // These elements are not extensible. If this flag is set, the object's 209 // Shape must also have the NotExtensible flag. This exists on 210 // ObjectElements in addition to Shape to simplify JIT code. 211 NOT_EXTENSIBLE = 0x10, 212 213 // These elements are set to integrity level "sealed". If this flag is 214 // set, the NOT_EXTENSIBLE flag must be set as well. 215 SEALED = 0x20, 216 217 // These elements are set to integrity level "frozen". If this flag is 218 // set, the SEALED flag must be set as well. 219 // 220 // This flag must only be set if the Shape has the FrozenElements flag. 221 // The Shape flag ensures a shape guard can be used to guard against frozen 222 // elements. The ObjectElements flag is convenient for JIT code and 223 // ObjectElements assertions. 224 FROZEN = 0x40, 225 226 // If this flag is not set, the elements are guaranteed to contain no hole 227 // values (the JS_ELEMENTS_HOLE MagicValue) in [0, initializedLength). 228 NON_PACKED = 0x80, 229 230 // If this flag is not set, there's definitely no for-in iterator that 231 // covers these dense elements so elements can be deleted without calling 232 // SuppressDeletedProperty. This is used by fast paths for various Array 233 // builtins. See also NativeObject::denseElementsMaybeInIteration. 234 MAYBE_IN_ITERATION = 0x100, 235 }; 236 237 // The flags word stores both the flags and the number of shifted elements. 238 // Allow shifting 2047 elements before actually moving the elements. 239 static const size_t NumShiftedElementsBits = 11; 240 static const size_t MaxShiftedElements = (1 << NumShiftedElementsBits) - 1; 241 static const size_t NumShiftedElementsShift = 32 - NumShiftedElementsBits; 242 static const size_t FlagsMask = (1 << NumShiftedElementsShift) - 1; 243 static_assert(MaxShiftedElements == 2047, 244 "MaxShiftedElements should match the comment"); 245 246 private: 247 friend class ::JSObject; 248 friend class ArrayObject; 249 friend class NativeObject; 250 friend class gc::TenuringTracer; 251 252 friend bool js::SetIntegrityLevel(JSContext* cx, HandleObject obj, 253 IntegrityLevel level); 254 255 friend bool ArraySetLength(JSContext* cx, Handle<ArrayObject*> obj, 256 HandleId id, Handle<PropertyDescriptor> desc, 257 ObjectOpResult& result); 258 259 // The NumShiftedElementsBits high bits of this are used to store the 260 // number of shifted elements, the other bits are available for the flags. 261 // See Flags enum above. 262 uint32_t flags; 263 264 /* 265 * Number of initialized elements. This is <= the capacity, and for arrays 266 * is <= the length. Memory for elements above the initialized length is 267 * uninitialized, but values between the initialized length and the proper 268 * length are conceptually holes. 269 */ 270 uint32_t initializedLength; 271 272 /* Number of allocated slots. */ 273 uint32_t capacity; 274 275 /* 'length' property of array objects, unused for other objects. */ 276 uint32_t length; 277 278 void setNonwritableArrayLength() { 279 // See ArrayObject::setNonWritableLength. 280 MOZ_ASSERT(capacity == initializedLength); 281 MOZ_ASSERT(numShiftedElements() == 0); 282 flags |= NONWRITABLE_ARRAY_LENGTH; 283 } 284 285 void addShiftedElements(uint32_t count) { 286 MOZ_ASSERT(count < capacity); 287 MOZ_ASSERT(count < initializedLength); 288 MOZ_ASSERT(!( 289 flags & (NONWRITABLE_ARRAY_LENGTH | NOT_EXTENSIBLE | SEALED | FROZEN))); 290 uint32_t numShifted = numShiftedElements() + count; 291 MOZ_ASSERT(numShifted <= MaxShiftedElements); 292 flags = (numShifted << NumShiftedElementsShift) | (flags & FlagsMask); 293 capacity -= count; 294 initializedLength -= count; 295 } 296 void unshiftShiftedElements(uint32_t count) { 297 MOZ_ASSERT(count > 0); 298 MOZ_ASSERT(!( 299 flags & (NONWRITABLE_ARRAY_LENGTH | NOT_EXTENSIBLE | SEALED | FROZEN))); 300 uint32_t numShifted = numShiftedElements(); 301 MOZ_ASSERT(count <= numShifted); 302 numShifted -= count; 303 flags = (numShifted << NumShiftedElementsShift) | (flags & FlagsMask); 304 capacity += count; 305 initializedLength += count; 306 } 307 void clearShiftedElements() { 308 flags &= FlagsMask; 309 MOZ_ASSERT(numShiftedElements() == 0); 310 } 311 312 void markNonPacked() { flags |= NON_PACKED; } 313 314 void markMaybeInIteration() { flags |= MAYBE_IN_ITERATION; } 315 316 void setNotExtensible() { 317 MOZ_ASSERT(!isNotExtensible()); 318 flags |= NOT_EXTENSIBLE; 319 } 320 321 void seal() { 322 MOZ_ASSERT(isNotExtensible()); 323 MOZ_ASSERT(!isSealed()); 324 MOZ_ASSERT(!isFrozen()); 325 flags |= SEALED; 326 } 327 void freeze() { 328 MOZ_ASSERT(isNotExtensible()); 329 MOZ_ASSERT(isSealed()); 330 MOZ_ASSERT(!isFrozen()); 331 flags |= FROZEN; 332 } 333 334 bool isFrozen() const { return flags & FROZEN; } 335 336 public: 337 constexpr ObjectElements(uint32_t capacity, uint32_t length) 338 : flags(0), initializedLength(0), capacity(capacity), length(length) {} 339 340 enum class SharedMemory { IsShared }; 341 342 constexpr ObjectElements(uint32_t capacity, uint32_t length, 343 SharedMemory shmem) 344 : flags(SHARED_MEMORY), 345 initializedLength(0), 346 capacity(capacity), 347 length(length) {} 348 349 HeapSlot* elements() { 350 return reinterpret_cast<HeapSlot*>(uintptr_t(this) + 351 sizeof(ObjectElements)); 352 } 353 const HeapSlot* elements() const { 354 return reinterpret_cast<const HeapSlot*>(uintptr_t(this) + 355 sizeof(ObjectElements)); 356 } 357 static ObjectElements* fromElements(HeapSlot* elems) { 358 return reinterpret_cast<ObjectElements*>(uintptr_t(elems) - 359 sizeof(ObjectElements)); 360 } 361 362 bool isSharedMemory() const { return flags & SHARED_MEMORY; } 363 364 static int offsetOfFlags() { 365 return int(offsetof(ObjectElements, flags)) - int(sizeof(ObjectElements)); 366 } 367 static int offsetOfInitializedLength() { 368 return int(offsetof(ObjectElements, initializedLength)) - 369 int(sizeof(ObjectElements)); 370 } 371 static int offsetOfCapacity() { 372 return int(offsetof(ObjectElements, capacity)) - 373 int(sizeof(ObjectElements)); 374 } 375 static int offsetOfLength() { 376 return int(offsetof(ObjectElements, length)) - int(sizeof(ObjectElements)); 377 } 378 379 static void PrepareForPreventExtensions(JSContext* cx, NativeObject* obj); 380 static void PreventExtensions(NativeObject* obj); 381 [[nodiscard]] static bool FreezeOrSeal(JSContext* cx, 382 Handle<NativeObject*> obj, 383 IntegrityLevel level); 384 385 bool isSealed() const { return flags & SEALED; } 386 387 bool isPacked() const { return !(flags & NON_PACKED); } 388 389 JS::PropertyAttributes elementAttributes() const { 390 if (isFrozen()) { 391 return {JS::PropertyAttribute::Enumerable}; 392 } 393 if (isSealed()) { 394 return {JS::PropertyAttribute::Enumerable, 395 JS::PropertyAttribute::Writable}; 396 } 397 return {JS::PropertyAttribute::Configurable, 398 JS::PropertyAttribute::Enumerable, JS::PropertyAttribute::Writable}; 399 } 400 401 uint32_t numShiftedElements() const { 402 uint32_t numShifted = flags >> NumShiftedElementsShift; 403 MOZ_ASSERT_IF(numShifted > 0, 404 !(flags & (NONWRITABLE_ARRAY_LENGTH | NOT_EXTENSIBLE | 405 SEALED | FROZEN))); 406 return numShifted; 407 } 408 409 uint32_t numAllocatedElements() const { 410 return VALUES_PER_HEADER + capacity + numShiftedElements(); 411 } 412 413 bool hasNonwritableArrayLength() const { 414 return flags & NONWRITABLE_ARRAY_LENGTH; 415 } 416 417 bool maybeInIteration() { return flags & MAYBE_IN_ITERATION; } 418 419 bool isNotExtensible() { return flags & NOT_EXTENSIBLE; } 420 421 // This is enough slots to store an object of this class. See the static 422 // assertion below. 423 static const size_t VALUES_PER_HEADER = 2; 424 425 #if defined(DEBUG) || defined(JS_JITSPEW) 426 void dumpStringContent(js::GenericPrinter& out) const; 427 #endif 428 }; 429 430 static_assert(ObjectElements::VALUES_PER_HEADER * sizeof(HeapSlot) == 431 sizeof(ObjectElements), 432 "ObjectElements doesn't fit in the given number of slots"); 433 434 /* 435 * Slots header used for native objects. The header stores the capacity and the 436 * slot data follows in memory. 437 */ 438 class alignas(HeapSlot) ObjectSlots { 439 uint32_t capacity_; 440 uint32_t dictionarySlotSpan_; 441 uint64_t maybeUniqueId_; 442 443 public: 444 // Special values for maybeUniqueId_ to indicate no unique ID is present. 445 static constexpr uint64_t NoUniqueIdInDynamicSlots = 0; 446 static constexpr uint64_t NoUniqueIdInSharedEmptySlots = 1; 447 static constexpr uint64_t LastNoUniqueIdValue = NoUniqueIdInSharedEmptySlots; 448 449 static constexpr size_t VALUES_PER_HEADER = 2; 450 451 static inline size_t allocCount(size_t slotCount) { 452 static_assert(sizeof(ObjectSlots) == 453 ObjectSlots::VALUES_PER_HEADER * sizeof(HeapSlot)); 454 #ifdef MOZ_VALGRIND 455 if (slotCount == 0) { 456 // Add an extra unused slot so that NativeObject::slots_ always points 457 // into the allocation otherwise valgrind thinks this is a leak. 458 slotCount = 1; 459 } 460 #endif 461 return slotCount + VALUES_PER_HEADER; 462 } 463 464 static inline size_t allocSize(size_t slotCount) { 465 return allocCount(slotCount) * sizeof(HeapSlot); 466 } 467 468 static ObjectSlots* fromSlots(HeapSlot* slots) { 469 MOZ_ASSERT(slots); 470 return reinterpret_cast<ObjectSlots*>(uintptr_t(slots) - 471 sizeof(ObjectSlots)); 472 } 473 474 static constexpr size_t offsetOfCapacity() { 475 return offsetof(ObjectSlots, capacity_); 476 } 477 static constexpr size_t offsetOfDictionarySlotSpan() { 478 return offsetof(ObjectSlots, dictionarySlotSpan_); 479 } 480 static constexpr size_t offsetOfMaybeUniqueId() { 481 return offsetof(ObjectSlots, maybeUniqueId_); 482 } 483 static constexpr size_t offsetOfSlots() { return sizeof(ObjectSlots); } 484 485 constexpr ObjectSlots(uint32_t capacity, uint32_t dictionarySlotSpan, 486 uint64_t maybeUniqueId); 487 488 constexpr uint32_t capacity() const { return capacity_; } 489 490 constexpr uint32_t dictionarySlotSpan() const { return dictionarySlotSpan_; } 491 492 bool isSharedEmptySlots() const { 493 return maybeUniqueId_ == NoUniqueIdInSharedEmptySlots; 494 } 495 496 constexpr bool hasUniqueId() const { 497 return maybeUniqueId_ > LastNoUniqueIdValue; 498 } 499 uint64_t uniqueId() const { 500 MOZ_ASSERT(hasUniqueId()); 501 return maybeUniqueId_; 502 } 503 uintptr_t maybeUniqueId() const { return hasUniqueId() ? maybeUniqueId_ : 0; } 504 void setUniqueId(uint64_t uid) { 505 MOZ_ASSERT(uid > LastNoUniqueIdValue); 506 MOZ_ASSERT(!isSharedEmptySlots()); 507 maybeUniqueId_ = uid; 508 } 509 510 void setDictionarySlotSpan(uint32_t span) { dictionarySlotSpan_ = span; } 511 512 HeapSlot* slots() const { 513 return reinterpret_cast<HeapSlot*>(uintptr_t(this) + sizeof(ObjectSlots)); 514 } 515 }; 516 517 /* 518 * Shared singletons for objects with no elements. 519 * emptyObjectElementsShared is used only for TypedArrays, when the TA 520 * maps shared memory. 521 */ 522 extern HeapSlot* const emptyObjectElements; 523 extern HeapSlot* const emptyObjectElementsShared; 524 525 /* 526 * Shared singletons for objects with no dynamic slots. 527 */ 528 extern HeapSlot* const emptyObjectSlots; 529 extern HeapSlot* const emptyObjectSlotsForDictionaryObject[]; 530 531 class AutoCheckShapeConsistency; 532 class GCMarker; 533 534 // Operations which change an object's dense elements can either succeed, fail, 535 // or be unable to complete. The latter is used when the object's elements must 536 // become sparse instead. The enum below is used for such operations. 537 enum class DenseElementResult { Failure, Success, Incomplete }; 538 539 // Stores a slot offset in bytes relative to either the NativeObject* address 540 // (if isFixedSlot) or to NativeObject::slots_ (if !isFixedSlot). 541 class TaggedSlotOffset { 542 uint32_t bits_ = 0; 543 544 public: 545 static constexpr size_t OffsetShift = 1; 546 static constexpr size_t IsFixedSlotFlag = 0b1; 547 548 static constexpr size_t MaxOffset = SHAPE_MAXIMUM_SLOT * sizeof(Value); 549 static_assert((uint64_t(MaxOffset) << OffsetShift) <= UINT32_MAX, 550 "maximum slot offset must fit in TaggedSlotOffset"); 551 552 constexpr TaggedSlotOffset() = default; 553 554 TaggedSlotOffset(uint32_t offset, bool isFixedSlot) 555 : bits_((offset << OffsetShift) | isFixedSlot) { 556 MOZ_ASSERT(offset <= MaxOffset); 557 } 558 559 uint32_t offset() const { return bits_ >> OffsetShift; } 560 bool isFixedSlot() const { return bits_ & IsFixedSlotFlag; } 561 562 bool operator==(const TaggedSlotOffset& other) const { 563 return bits_ == other.bits_; 564 } 565 bool operator!=(const TaggedSlotOffset& other) const { 566 return !(*this == other); 567 } 568 }; 569 570 enum class CanReuseShape { 571 // The Shape can be reused. This implies CanReusePropMap. 572 CanReuseShape, 573 574 // Only the PropMap can be reused. 575 CanReusePropMap, 576 577 // Neither the PropMap nor Shape can be reused. 578 NoReuse, 579 }; 580 581 /* 582 * [SMDOC] NativeObject layout 583 * 584 * NativeObject specifies the internal implementation of a native object. 585 * 586 * Native objects use ShapedObject::shape to record property information. Two 587 * native objects with the same shape are guaranteed to have the same number of 588 * fixed slots. 589 * 590 * Native objects extend the base implementation of an object with storage for 591 * the object's named properties and indexed elements. 592 * 593 * These are stored separately from one another. Objects are followed by a 594 * variable-sized array of values for inline storage, which may be used by 595 * either properties of native objects (fixed slots), by elements (fixed 596 * elements), or by other data for certain kinds of objects, such as 597 * ArrayBufferObjects and TypedArrayObjects. 598 * 599 * Named property storage can be split between fixed slots and a dynamically 600 * allocated array (the slots member). For an object with N fixed slots, shapes 601 * with slots [0..N-1] are stored in the fixed slots, and the remainder are 602 * stored in the dynamic array. If all properties fit in the fixed slots, the 603 * 'slots_' member is nullptr. 604 * 605 * Elements are indexed via the 'elements_' member. This member can point to 606 * either the shared emptyObjectElements and emptyObjectElementsShared 607 * singletons, into the inline value array (the address of the third value, to 608 * leave room for a ObjectElements header;in this case numFixedSlots() is zero) 609 * or to a dynamically allocated array. 610 * 611 * Slots and elements may both be non-empty. The slots may be either names or 612 * indexes; no indexed property will be in both the slots and elements. 613 */ 614 class NativeObject : public JSObject { 615 protected: 616 /* Slots for object properties. */ 617 js::HeapSlot* slots_; 618 619 /* Slots for object dense elements. */ 620 js::HeapSlot* elements_; 621 622 friend class ::JSObject; 623 624 private: 625 static void staticAsserts() { 626 static_assert(sizeof(NativeObject) == sizeof(JSObject_Slots0), 627 "native object size must match GC thing size"); 628 static_assert(sizeof(NativeObject) == sizeof(JS::shadow::Object), 629 "shadow interface must match actual implementation"); 630 static_assert(sizeof(NativeObject) % sizeof(Value) == 0, 631 "fixed slots after an object must be aligned"); 632 633 static_assert(offsetOfShape() == offsetof(JS::shadow::Object, shape), 634 "shadow type must match actual type"); 635 static_assert( 636 offsetof(NativeObject, slots_) == offsetof(JS::shadow::Object, slots), 637 "shadow slots must match actual slots"); 638 static_assert( 639 offsetof(NativeObject, elements_) == offsetof(JS::shadow::Object, _1), 640 "shadow placeholder must match actual elements"); 641 642 static_assert(MAX_FIXED_SLOTS <= Shape::FIXED_SLOTS_MAX, 643 "verify numFixedSlots() bitfield is big enough"); 644 static_assert(sizeof(NativeObject) + MAX_FIXED_SLOTS * sizeof(Value) == 645 JSObject::MAX_BYTE_SIZE, 646 "inconsistent maximum object size"); 647 648 // Sanity check NativeObject size is what we expect. 649 #ifdef JS_64BIT 650 static_assert(sizeof(NativeObject) == 3 * sizeof(void*)); 651 #else 652 static_assert(sizeof(NativeObject) == 4 * sizeof(void*)); 653 #endif 654 } 655 656 public: 657 NativeShape* shape() const { return &JSObject::shape()->asNative(); } 658 SharedShape* sharedShape() const { return &shape()->asShared(); } 659 DictionaryShape* dictionaryShape() const { return &shape()->asDictionary(); } 660 661 PropertyInfoWithKey getLastProperty() const { 662 return shape()->lastProperty(); 663 } 664 665 HeapSlotArray getDenseElements() const { return HeapSlotArray(elements_); } 666 667 const Value& getDenseElement(uint32_t idx) const { 668 MOZ_ASSERT(idx < getDenseInitializedLength()); 669 return elements_[idx]; 670 } 671 bool containsDenseElement(uint32_t idx) const { 672 return idx < getDenseInitializedLength() && 673 !elements_[idx].isMagic(JS_ELEMENTS_HOLE); 674 } 675 uint32_t getDenseInitializedLength() const { 676 return getElementsHeader()->initializedLength; 677 } 678 uint32_t getDenseCapacity() const { return getElementsHeader()->capacity; } 679 680 bool isSharedMemory() const { return getElementsHeader()->isSharedMemory(); } 681 682 // Update the object's shape and allocate slots if needed to match the shape's 683 // slot span. 684 MOZ_ALWAYS_INLINE bool setShapeAndAddNewSlots(JSContext* cx, 685 SharedShape* newShape, 686 uint32_t oldSpan, 687 uint32_t newSpan); 688 689 // Methods optimized for adding/removing a single slot. Must only be used for 690 // non-dictionary objects. 691 MOZ_ALWAYS_INLINE bool setShapeAndAddNewSlot(JSContext* cx, 692 SharedShape* newShape, 693 uint32_t slot); 694 void setShapeAndRemoveLastSlot(JSContext* cx, SharedShape* newShape, 695 uint32_t slot); 696 697 MOZ_ALWAYS_INLINE CanReuseShape 698 canReuseShapeForNewProperties(NativeShape* newShape) const { 699 NativeShape* oldShape = shape(); 700 MOZ_ASSERT(oldShape->propMapLength() == 0, 701 "object must have no properties"); 702 MOZ_ASSERT(newShape->propMapLength() > 0, 703 "new shape must have at least one property"); 704 if (oldShape->isDictionary() || newShape->isDictionary()) { 705 return CanReuseShape::NoReuse; 706 } 707 // We only handle the common case where the old shape has no object flags 708 // (expected because it's an empty object) and the new shape has just the 709 // HasEnumerable flag that we can copy safely. 710 if (!oldShape->objectFlags().isEmpty()) { 711 return CanReuseShape::NoReuse; 712 } 713 MOZ_ASSERT(newShape->hasObjectFlag(ObjectFlag::HasEnumerable)); 714 if (newShape->objectFlags() != ObjectFlags({ObjectFlag::HasEnumerable})) { 715 return CanReuseShape::NoReuse; 716 } 717 // If the number of fixed slots or the BaseShape is different, we can't 718 // reuse the Shape but we can still reuse the PropMap. 719 if (oldShape->numFixedSlots() != newShape->numFixedSlots() || 720 oldShape->base() != newShape->base()) { 721 return CanReuseShape::CanReusePropMap; 722 } 723 MOZ_ASSERT(oldShape->getObjectClass() == newShape->getObjectClass()); 724 MOZ_ASSERT(oldShape->proto() == newShape->proto()); 725 MOZ_ASSERT(oldShape->realm() == newShape->realm()); 726 return CanReuseShape::CanReuseShape; 727 } 728 729 // Newly-created TypedArrays that map a SharedArrayBuffer are 730 // marked as shared by giving them an ObjectElements that has the 731 // ObjectElements::SHARED_MEMORY flag set. 732 void setIsSharedMemory() { 733 MOZ_ASSERT(elements_ == emptyObjectElements); 734 elements_ = emptyObjectElementsShared; 735 } 736 737 static inline NativeObject* create(JSContext* cx, gc::AllocKind kind, 738 gc::Heap heap, Handle<SharedShape*> shape, 739 gc::AllocSite* site = nullptr); 740 741 template <typename T> 742 static inline T* create(JSContext* cx, gc::AllocKind kind, gc::Heap heap, 743 Handle<SharedShape*> shape, 744 gc::AllocSite* site = nullptr) { 745 NativeObject* nobj = create(cx, kind, heap, shape, site); 746 return nobj ? &nobj->as<T>() : nullptr; 747 } 748 749 #ifdef DEBUG 750 static void enableShapeConsistencyChecks(); 751 #endif 752 753 protected: 754 #ifdef DEBUG 755 friend class js::AutoCheckShapeConsistency; 756 void checkShapeConsistency(); 757 #else 758 void checkShapeConsistency() {} 759 #endif 760 761 void maybeFreeDictionaryPropSlots(JSContext* cx, DictionaryPropMap* map, 762 uint32_t mapLength); 763 764 [[nodiscard]] static bool toDictionaryMode(JSContext* cx, 765 Handle<NativeObject*> obj); 766 767 private: 768 inline void setEmptyDynamicSlots(uint32_t dictonarySlotSpan); 769 770 inline void setDictionaryModeSlotSpan(uint32_t span); 771 772 friend class gc::TenuringTracer; 773 774 // Given a slot range from |start| to |end| exclusive, call |fun| with 775 // pointers to the corresponding fixed slot and/or dynamic slot ranges. 776 template <typename Fun> 777 void forEachSlotRangeUnchecked(uint32_t start, uint32_t end, const Fun& fun) { 778 MOZ_ASSERT(end >= start); 779 uint32_t nfixed = numFixedSlots(); 780 if (start < nfixed) { 781 HeapSlot* fixedStart = &fixedSlots()[start]; 782 HeapSlot* fixedEnd = &fixedSlots()[std::min(nfixed, end)]; 783 fun(fixedStart, fixedEnd); 784 start = nfixed; 785 } 786 if (end > nfixed) { 787 HeapSlot* dynStart = &slots_[start - nfixed]; 788 HeapSlot* dynEnd = &slots_[end - nfixed]; 789 fun(dynStart, dynEnd); 790 } 791 } 792 793 template <typename Fun> 794 void forEachSlotRange(uint32_t start, uint32_t end, const Fun& fun) { 795 MOZ_ASSERT(slotInRange(end, SENTINEL_ALLOWED)); 796 forEachSlotRangeUnchecked(start, end, fun); 797 } 798 799 protected: 800 friend class DictionaryPropMap; 801 friend class GCMarker; 802 friend class Shape; 803 804 void invalidateSlotRange(uint32_t start, uint32_t end) { 805 #ifdef DEBUG 806 forEachSlotRange(start, end, [](HeapSlot* slotsStart, HeapSlot* slotsEnd) { 807 Debug_SetSlotRangeToCrashOnTouch(slotsStart, slotsEnd); 808 }); 809 #endif /* DEBUG */ 810 } 811 812 void initFixedSlots(uint32_t numSlots) { 813 MOZ_ASSERT(numSlots == numUsedFixedSlots()); 814 HeapSlot* slots = fixedSlots(); 815 for (uint32_t i = 0; i < numSlots; i++) { 816 slots[i].initAsUndefined(); 817 } 818 } 819 void initDynamicSlots(uint32_t numSlots) { 820 MOZ_ASSERT(numSlots == sharedShape()->slotSpan() - numFixedSlots()); 821 HeapSlot* slots = slots_; 822 for (uint32_t i = 0; i < numSlots; i++) { 823 slots[i].initAsUndefined(); 824 } 825 } 826 void initSlots(uint32_t nfixed, uint32_t slotSpan) { 827 initFixedSlots(std::min(nfixed, slotSpan)); 828 if (slotSpan > nfixed) { 829 initDynamicSlots(slotSpan - nfixed); 830 } 831 } 832 833 #ifdef DEBUG 834 enum SentinelAllowed { SENTINEL_NOT_ALLOWED, SENTINEL_ALLOWED }; 835 836 /* 837 * Check that slot is in range for the object's allocated slots. 838 * If sentinelAllowed then slot may equal the slot capacity. 839 */ 840 bool slotInRange(uint32_t slot, 841 SentinelAllowed sentinel = SENTINEL_NOT_ALLOWED) const; 842 843 /* 844 * Check whether a slot is a fixed slot. 845 */ 846 bool slotIsFixed(uint32_t slot) const; 847 848 /* 849 * Check whether the supplied number of fixed slots is correct. 850 */ 851 bool isNumFixedSlots(uint32_t nfixed) const; 852 #endif 853 854 /* 855 * Minimum size for dynamically allocated slots in normal Objects. 856 * ArrayObjects don't use this limit and can have a lower slot capacity, 857 * since they normally don't have a lot of slots. 858 */ 859 static const uint32_t SLOT_CAPACITY_MIN = 6; 860 861 /* 862 * Minimum size for dynamically allocated elements in normal Objects. 863 */ 864 static const uint32_t ELEMENT_CAPACITY_MIN = 6; 865 866 HeapSlot* fixedSlots() const { 867 return reinterpret_cast<HeapSlot*>(uintptr_t(this) + sizeof(NativeObject)); 868 } 869 870 public: 871 inline void initEmptyDynamicSlots(); 872 873 [[nodiscard]] static bool generateNewDictionaryShape( 874 JSContext* cx, Handle<NativeObject*> obj); 875 876 // The maximum number of slots in an object. 877 // |MAX_SLOTS_COUNT * sizeof(JS::Value)| shouldn't overflow 878 // int32_t (see slotsSizeMustNotOverflow). 879 static const uint32_t MAX_SLOTS_COUNT = (1 << 28) - 1; 880 881 static void slotsSizeMustNotOverflow() { 882 static_assert( 883 NativeObject::MAX_SLOTS_COUNT <= INT32_MAX / sizeof(JS::Value), 884 "every caller of this method requires that a slot " 885 "number (or slot count) count multiplied by " 886 "sizeof(Value) can't overflow uint32_t (and sometimes " 887 "int32_t, too)"); 888 } 889 890 uint32_t numFixedSlots() const { 891 return reinterpret_cast<const JS::shadow::Object*>(this)->numFixedSlots(); 892 } 893 894 // Get the number of fixed slots when the shape pointer may have been 895 // forwarded by a moving GC. You need to use this rather that 896 // numFixedSlots() in a trace hook if you access an object that is not the 897 // object being traced, since it may have a stale shape pointer. 898 inline uint32_t numFixedSlotsMaybeForwarded() const; 899 900 uint32_t numUsedFixedSlots() const { 901 uint32_t nslots = sharedShape()->slotSpan(); 902 return std::min(nslots, numFixedSlots()); 903 } 904 905 uint32_t slotSpan() const { 906 if (inDictionaryMode()) { 907 return dictionaryModeSlotSpan(); 908 } 909 MOZ_ASSERT(getSlotsHeader()->dictionarySlotSpan() == 0); 910 return sharedShape()->slotSpan(); 911 } 912 913 uint32_t dictionaryModeSlotSpan() const { 914 MOZ_ASSERT(inDictionaryMode()); 915 return getSlotsHeader()->dictionarySlotSpan(); 916 } 917 918 /* Whether a slot is at a fixed offset from this object. */ 919 bool isFixedSlot(size_t slot) { return slot < numFixedSlots(); } 920 921 /* Index into the dynamic slots array to use for a dynamic slot. */ 922 size_t dynamicSlotIndex(size_t slot) { 923 MOZ_ASSERT(slot >= numFixedSlots()); 924 return slot - numFixedSlots(); 925 } 926 927 // Native objects are never proxies. Call isExtensible instead. 928 bool nonProxyIsExtensible() const = delete; 929 930 bool isExtensible() const { return !hasFlag(ObjectFlag::NotExtensible); } 931 932 /* 933 * Whether there may be indexed properties on this object, excluding any in 934 * the object's elements. 935 */ 936 bool isIndexed() const { return hasFlag(ObjectFlag::Indexed); } 937 938 bool hasInterestingSymbol() const { 939 return hasFlag(ObjectFlag::HasInterestingSymbol); 940 } 941 942 bool hasEnumerableProperty() const { 943 return hasFlag(ObjectFlag::HasEnumerable); 944 } 945 946 static bool setHadGetterSetterChange(JSContext* cx, 947 Handle<NativeObject*> obj) { 948 return setFlag(cx, obj, ObjectFlag::HadGetterSetterChange); 949 } 950 bool hadGetterSetterChange() const { 951 return hasFlag(ObjectFlag::HadGetterSetterChange); 952 } 953 954 static bool setHasObjectFuse(JSContext* cx, Handle<NativeObject*> obj) { 955 return setFlag(cx, obj, js::ObjectFlag::HasObjectFuse); 956 } 957 958 bool allocateInitialSlots(JSContext* cx, uint32_t capacity); 959 960 /* 961 * Grow or shrink slots immediately before changing the slot span. 962 * The number of allocated slots is not stored explicitly, and changes to 963 * the slots must track changes in the slot span. 964 */ 965 bool growSlots(JSContext* cx, uint32_t oldCapacity, uint32_t newCapacity); 966 bool growSlotsForNewSlot(JSContext* cx, uint32_t numFixed, uint32_t slot); 967 void shrinkSlots(JSContext* cx, uint32_t oldCapacity, uint32_t newCapacity); 968 969 bool allocateSlots(Nursery& nursery, uint32_t newCapacity); 970 971 /* 972 * This method is static because it's called from JIT code. On OOM, returns 973 * false without leaving a pending exception on the context. 974 */ 975 static bool growSlotsPure(JSContext* cx, NativeObject* obj, 976 uint32_t newCapacity); 977 978 /* 979 * Like growSlotsPure but for dense elements. This will return 980 * false if we failed to allocate a dense element for some reason (OOM, too 981 * many dense elements, non-writable array length, etc). 982 */ 983 static bool addDenseElementPure(JSContext* cx, NativeObject* obj); 984 985 /* 986 * Indicates whether this object has an ObjectSlots allocation attached. The 987 * capacity of this can be zero if it is only used to hold a unique ID. 988 */ 989 bool hasDynamicSlots() const { 990 return !getSlotsHeader()->isSharedEmptySlots(); 991 } 992 993 /* Compute the number of dynamic slots required for this object. */ 994 MOZ_ALWAYS_INLINE uint32_t calculateDynamicSlots() const; 995 996 MOZ_ALWAYS_INLINE uint32_t numDynamicSlots() const; 997 998 #ifdef DEBUG 999 uint32_t outOfLineNumDynamicSlots() const; 1000 #endif 1001 1002 bool empty() const { return shape()->propMapLength() == 0; } 1003 1004 mozilla::Maybe<PropertyInfo> lookup(JSContext* cx, jsid id); 1005 mozilla::Maybe<PropertyInfo> lookup(JSContext* cx, PropertyName* name) { 1006 return lookup(cx, NameToId(name)); 1007 } 1008 1009 bool contains(JSContext* cx, jsid id) { return lookup(cx, id).isSome(); } 1010 bool contains(JSContext* cx, PropertyName* name) { 1011 return lookup(cx, name).isSome(); 1012 } 1013 bool contains(JSContext* cx, jsid id, PropertyInfo prop) { 1014 mozilla::Maybe<PropertyInfo> found = lookup(cx, id); 1015 return found.isSome() && *found == prop; 1016 } 1017 1018 /* Contextless; can be called from other pure code. */ 1019 mozilla::Maybe<PropertyInfo> lookupPure(jsid id); 1020 mozilla::Maybe<PropertyInfo> lookupPure(PropertyName* name) { 1021 return lookupPure(NameToId(name)); 1022 } 1023 1024 bool containsPure(jsid id) { return lookupPure(id).isSome(); } 1025 bool containsPure(PropertyName* name) { return containsPure(NameToId(name)); } 1026 bool containsPure(jsid id, PropertyInfo prop) { 1027 mozilla::Maybe<PropertyInfo> found = lookupPure(id); 1028 return found.isSome() && *found == prop; 1029 } 1030 1031 private: 1032 /* 1033 * Allocate and free an object slot. 1034 * 1035 * FIXME: bug 593129 -- slot allocation should be done by object methods 1036 * after calling object-parameter-free shape methods, avoiding coupling 1037 * logic across the object vs. shape module wall. 1038 */ 1039 static bool allocDictionarySlot(JSContext* cx, Handle<NativeObject*> obj, 1040 uint32_t* slotp); 1041 1042 void freeDictionarySlot(uint32_t slot); 1043 1044 static MOZ_ALWAYS_INLINE bool maybeConvertToDictionaryForAdd( 1045 JSContext* cx, Handle<NativeObject*> obj); 1046 1047 public: 1048 // Add a new property. Must only be used when the |id| is not already present 1049 // in the object's shape. Checks for non-extensibility must be done by the 1050 // callers. 1051 static bool addProperty(JSContext* cx, Handle<NativeObject*> obj, HandleId id, 1052 PropertyFlags flags, uint32_t* slotOut); 1053 1054 static bool addProperty(JSContext* cx, Handle<NativeObject*> obj, 1055 Handle<PropertyName*> name, PropertyFlags flags, 1056 uint32_t* slotOut) { 1057 RootedId id(cx, NameToId(name)); 1058 return addProperty(cx, obj, id, flags, slotOut); 1059 } 1060 1061 static bool addPropertyInReservedSlot(JSContext* cx, 1062 Handle<NativeObject*> obj, HandleId id, 1063 uint32_t slot, PropertyFlags flags); 1064 static bool addPropertyInReservedSlot(JSContext* cx, 1065 Handle<NativeObject*> obj, 1066 Handle<PropertyName*> name, 1067 uint32_t slot, PropertyFlags flags) { 1068 RootedId id(cx, NameToId(name)); 1069 return addPropertyInReservedSlot(cx, obj, id, slot, flags); 1070 } 1071 1072 static bool addCustomDataProperty(JSContext* cx, Handle<NativeObject*> obj, 1073 HandleId id, PropertyFlags flags); 1074 1075 // Change a property with key |id| in this object. The object must already 1076 // have a property (stored in the shape tree) with this |id|. 1077 static bool changeProperty(JSContext* cx, Handle<NativeObject*> obj, 1078 HandleId id, PropertyFlags flags, 1079 uint32_t* slotOut); 1080 1081 static bool changeCustomDataPropAttributes(JSContext* cx, 1082 Handle<NativeObject*> obj, 1083 HandleId id, PropertyFlags flags); 1084 1085 // Remove the property named by id from this object. 1086 static bool removeProperty(JSContext* cx, Handle<NativeObject*> obj, 1087 HandleId id); 1088 1089 static bool freezeOrSealProperties(JSContext* cx, Handle<NativeObject*> obj, 1090 IntegrityLevel level); 1091 1092 protected: 1093 static bool changeNumFixedSlotsAfterSwap(JSContext* cx, 1094 Handle<NativeObject*> obj, 1095 uint32_t nfixed); 1096 1097 // For use from JSObject::swap. 1098 [[nodiscard]] bool prepareForSwap(JSContext* cx, JSObject* other, 1099 MutableHandleValueVector slotValuesOut); 1100 [[nodiscard]] static bool fixupAfterSwap(JSContext* cx, 1101 Handle<NativeObject*> obj, 1102 gc::AllocKind kind, 1103 HandleValueVector slotValues); 1104 1105 public: 1106 // Return true if this object has been converted from shared-immutable 1107 // shapes to object-owned dictionary shapes. 1108 bool inDictionaryMode() const { return shape()->isDictionary(); } 1109 1110 const Value& getSlot(uint32_t slot) const { 1111 MOZ_ASSERT(slotInRange(slot)); 1112 uint32_t fixed = numFixedSlots(); 1113 if (slot < fixed) { 1114 return fixedSlots()[slot]; 1115 } 1116 return slots_[slot - fixed]; 1117 } 1118 1119 const HeapSlot* getSlotAddressUnchecked(uint32_t slot) const { 1120 uint32_t fixed = numFixedSlots(); 1121 if (slot < fixed) { 1122 return fixedSlots() + slot; 1123 } 1124 return slots_ + (slot - fixed); 1125 } 1126 1127 HeapSlot* getSlotAddressUnchecked(uint32_t slot) { 1128 uint32_t fixed = numFixedSlots(); 1129 if (slot < fixed) { 1130 return fixedSlots() + slot; 1131 } 1132 return slots_ + (slot - fixed); 1133 } 1134 1135 HeapSlot* getSlotsUnchecked() { return slots_; } 1136 1137 HeapSlot* getSlotAddress(uint32_t slot) { 1138 /* 1139 * This can be used to get the address of the end of the slots for the 1140 * object, which may be necessary when fetching zero-length arrays of 1141 * slots (e.g. for callObjVarArray). 1142 */ 1143 MOZ_ASSERT(slotInRange(slot, SENTINEL_ALLOWED)); 1144 return getSlotAddressUnchecked(slot); 1145 } 1146 1147 const HeapSlot* getSlotAddress(uint32_t slot) const { 1148 /* 1149 * This can be used to get the address of the end of the slots for the 1150 * object, which may be necessary when fetching zero-length arrays of 1151 * slots (e.g. for callObjVarArray). 1152 */ 1153 MOZ_ASSERT(slotInRange(slot, SENTINEL_ALLOWED)); 1154 return getSlotAddressUnchecked(slot); 1155 } 1156 1157 MOZ_ALWAYS_INLINE HeapSlot& getSlotRef(uint32_t slot) { 1158 MOZ_ASSERT(slotInRange(slot)); 1159 return *getSlotAddress(slot); 1160 } 1161 1162 MOZ_ALWAYS_INLINE const HeapSlot& getSlotRef(uint32_t slot) const { 1163 MOZ_ASSERT(slotInRange(slot)); 1164 return *getSlotAddress(slot); 1165 } 1166 1167 // Check requirements on values stored to this object. 1168 MOZ_ALWAYS_INLINE void checkStoredValue(const Value& v) { 1169 MOZ_ASSERT(IsObjectValueInCompartment(v, compartment())); 1170 MOZ_ASSERT(AtomIsMarked(zoneFromAnyThread(), v)); 1171 MOZ_ASSERT_IF(v.isMagic() && v.whyMagic() == JS_ELEMENTS_HOLE, 1172 !denseElementsArePacked()); 1173 } 1174 1175 MOZ_ALWAYS_INLINE void setSlot(uint32_t slot, const Value& value) { 1176 MOZ_ASSERT(slotInRange(slot)); 1177 checkStoredValue(value); 1178 getSlotRef(slot).set(this, HeapSlot::Slot, slot, value); 1179 } 1180 1181 MOZ_ALWAYS_INLINE void initSlot(uint32_t slot, const Value& value) { 1182 MOZ_ASSERT(getSlot(slot).isUndefined()); 1183 MOZ_ASSERT(slotInRange(slot)); 1184 checkStoredValue(value); 1185 initSlotUnchecked(slot, value); 1186 } 1187 1188 MOZ_ALWAYS_INLINE void initSlotUnchecked(uint32_t slot, const Value& value) { 1189 getSlotAddressUnchecked(slot)->init(this, HeapSlot::Slot, slot, value); 1190 } 1191 1192 // Returns the GetterSetter for an accessor property. 1193 GetterSetter* getGetterSetter(uint32_t slot) const { 1194 return getSlot(slot).toGCThing()->as<GetterSetter>(); 1195 } 1196 GetterSetter* getGetterSetter(PropertyInfo prop) const { 1197 MOZ_ASSERT(prop.isAccessorProperty()); 1198 return getGetterSetter(prop.slot()); 1199 } 1200 1201 // Returns the (possibly nullptr) getter or setter object. |prop| and |slot| 1202 // must be (for) an accessor property. 1203 JSObject* getGetter(uint32_t slot) const { 1204 return getGetterSetter(slot)->getter(); 1205 } 1206 JSObject* getGetter(PropertyInfo prop) const { 1207 return getGetterSetter(prop)->getter(); 1208 } 1209 JSObject* getSetter(PropertyInfo prop) const { 1210 return getGetterSetter(prop)->setter(); 1211 } 1212 1213 // Returns true if the property has a non-nullptr getter or setter object. 1214 // |prop| can be any property. 1215 bool hasGetter(PropertyInfo prop) const { 1216 return prop.isAccessorProperty() && getGetter(prop); 1217 } 1218 bool hasSetter(PropertyInfo prop) const { 1219 return prop.isAccessorProperty() && getSetter(prop); 1220 } 1221 1222 // If the property has a non-nullptr getter/setter, return it as ObjectValue. 1223 // Else return |undefined|. |prop| must be an accessor property. 1224 Value getGetterValue(PropertyInfo prop) const { 1225 MOZ_ASSERT(prop.isAccessorProperty()); 1226 if (JSObject* getterObj = getGetter(prop)) { 1227 return ObjectValue(*getterObj); 1228 } 1229 return UndefinedValue(); 1230 } 1231 Value getSetterValue(PropertyInfo prop) const { 1232 MOZ_ASSERT(prop.isAccessorProperty()); 1233 if (JSObject* setterObj = getSetter(prop)) { 1234 return ObjectValue(*setterObj); 1235 } 1236 return UndefinedValue(); 1237 } 1238 1239 [[nodiscard]] bool setUniqueId(JSRuntime* runtime, uint64_t uid); 1240 inline bool hasUniqueId() const { return getSlotsHeader()->hasUniqueId(); } 1241 inline uint64_t uniqueId() const { return getSlotsHeader()->uniqueId(); } 1242 inline uint64_t maybeUniqueId() const { 1243 return getSlotsHeader()->maybeUniqueId(); 1244 } 1245 bool setOrUpdateUniqueId(JSContext* cx, uint64_t uid); 1246 1247 // MAX_FIXED_SLOTS is the biggest number of fixed slots our GC 1248 // size classes will give an object. 1249 static constexpr uint32_t MAX_FIXED_SLOTS = 1250 JS::shadow::Object::MAX_FIXED_SLOTS; 1251 1252 private: 1253 void prepareElementRangeForOverwrite(size_t start, size_t end) { 1254 MOZ_ASSERT(end <= getDenseInitializedLength()); 1255 for (size_t i = start; i < end; i++) { 1256 elements_[i].destroy(); 1257 } 1258 } 1259 1260 /* 1261 * Trigger the write barrier on a range of slots that will no longer be 1262 * reachable. 1263 */ 1264 void prepareSlotRangeForOverwrite(size_t start, size_t end) { 1265 for (size_t i = start; i < end; i++) { 1266 getSlotAddressUnchecked(i)->destroy(); 1267 } 1268 } 1269 1270 inline void shiftDenseElementsUnchecked(uint32_t count); 1271 1272 // Like getSlotRef, but optimized for reserved slots. This relies on the fact 1273 // that the first reserved slots (up to MAX_FIXED_SLOTS) are always stored in 1274 // fixed slots. This lets the compiler optimize away the branch below when 1275 // |index| is a constant (after inlining). 1276 // 1277 // Note: objects that may be swapped have less predictable slot layouts 1278 // because they could have been swapped with an object with fewer fixed slots. 1279 // Fortunately, the only native objects that can be swapped are DOM objects 1280 // and these shouldn't end up here (asserted below). 1281 MOZ_ALWAYS_INLINE HeapSlot& getReservedSlotRef(uint32_t index) { 1282 MOZ_ASSERT(index < JSSLOT_FREE(getClass())); 1283 MOZ_ASSERT(slotIsFixed(index) == (index < MAX_FIXED_SLOTS)); 1284 MOZ_ASSERT(!ObjectMayBeSwapped(this)); 1285 return index < MAX_FIXED_SLOTS ? fixedSlots()[index] 1286 : slots_[index - MAX_FIXED_SLOTS]; 1287 } 1288 MOZ_ALWAYS_INLINE const HeapSlot& getReservedSlotRef(uint32_t index) const { 1289 MOZ_ASSERT(index < JSSLOT_FREE(getClass())); 1290 MOZ_ASSERT(slotIsFixed(index) == (index < MAX_FIXED_SLOTS)); 1291 MOZ_ASSERT(!ObjectMayBeSwapped(this)); 1292 return index < MAX_FIXED_SLOTS ? fixedSlots()[index] 1293 : slots_[index - MAX_FIXED_SLOTS]; 1294 } 1295 1296 public: 1297 MOZ_ALWAYS_INLINE const Value& getReservedSlot(uint32_t index) const { 1298 return getReservedSlotRef(index); 1299 } 1300 MOZ_ALWAYS_INLINE void initReservedSlot(uint32_t index, const Value& v) { 1301 MOZ_ASSERT(getReservedSlot(index).isUndefined()); 1302 checkStoredValue(v); 1303 getReservedSlotRef(index).init(this, HeapSlot::Slot, index, v); 1304 } 1305 MOZ_ALWAYS_INLINE void setReservedSlot(uint32_t index, const Value& v) { 1306 checkStoredValue(v); 1307 getReservedSlotRef(index).set(this, HeapSlot::Slot, index, v); 1308 } 1309 1310 // For slots which are known to always be fixed, due to the way they are 1311 // allocated. 1312 1313 HeapSlot& getFixedSlotRef(uint32_t slot) { 1314 MOZ_ASSERT(slotIsFixed(slot)); 1315 return fixedSlots()[slot]; 1316 } 1317 1318 const Value& getFixedSlot(uint32_t slot) const { 1319 MOZ_ASSERT(slotIsFixed(slot)); 1320 return fixedSlots()[slot]; 1321 } 1322 1323 const Value& getDynamicSlot(uint32_t dynamicSlotIndex) const { 1324 MOZ_ASSERT(dynamicSlotIndex < outOfLineNumDynamicSlots()); 1325 return slots_[dynamicSlotIndex]; 1326 } 1327 1328 void setFixedSlot(uint32_t slot, const Value& value) { 1329 MOZ_ASSERT(slotIsFixed(slot)); 1330 checkStoredValue(value); 1331 fixedSlots()[slot].set(this, HeapSlot::Slot, slot, value); 1332 } 1333 1334 void setDynamicSlot(uint32_t numFixed, uint32_t slot, const Value& value) { 1335 MOZ_ASSERT(numFixedSlots() == numFixed); 1336 MOZ_ASSERT(slot >= numFixed); 1337 MOZ_ASSERT(slot - numFixed < getSlotsHeader()->capacity()); 1338 checkStoredValue(value); 1339 slots_[slot - numFixed].set(this, HeapSlot::Slot, slot, value); 1340 } 1341 1342 void initFixedSlot(uint32_t slot, const Value& value) { 1343 MOZ_ASSERT(slotIsFixed(slot)); 1344 checkStoredValue(value); 1345 fixedSlots()[slot].init(this, HeapSlot::Slot, slot, value); 1346 } 1347 1348 void initDynamicSlot(uint32_t numFixed, uint32_t slot, const Value& value) { 1349 MOZ_ASSERT(numFixedSlots() == numFixed); 1350 MOZ_ASSERT(slot >= numFixed); 1351 MOZ_ASSERT(slot - numFixed < getSlotsHeader()->capacity()); 1352 checkStoredValue(value); 1353 slots_[slot - numFixed].init(this, HeapSlot::Slot, slot, value); 1354 } 1355 1356 template <typename T> 1357 T* maybePtrFromReservedSlot(uint32_t slot) const { 1358 Value v = getReservedSlot(slot); 1359 return v.isUndefined() ? nullptr : static_cast<T*>(v.toPrivate()); 1360 } 1361 1362 // Returns the address of a reserved fixed slot that stores a T* as 1363 // PrivateValue. Be very careful when using this because the object might be 1364 // moved in memory! 1365 template <typename T> 1366 T** addressOfFixedSlotPrivatePtr(size_t slot) { 1367 MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(getClass())); 1368 MOZ_ASSERT(slotIsFixed(slot)); 1369 MOZ_ASSERT(getReservedSlot(slot).isDouble()); 1370 void* addr = &getFixedSlotRef(slot); 1371 return reinterpret_cast<T**>(addr); 1372 } 1373 1374 /* 1375 * Calculate the number of dynamic slots to allocate to cover the properties 1376 * in an object with the given number of fixed slots and slot span. 1377 */ 1378 static MOZ_ALWAYS_INLINE uint32_t calculateDynamicSlots(uint32_t nfixed, 1379 uint32_t span, 1380 const JSClass* clasp); 1381 static MOZ_ALWAYS_INLINE uint32_t calculateDynamicSlots(SharedShape* shape); 1382 1383 ObjectSlots* getSlotsHeader() const { return ObjectSlots::fromSlots(slots_); } 1384 1385 /* Elements accessors. */ 1386 1387 // The maximum size, in sizeof(Value), of the allocation used for an 1388 // object's dense elements. (This includes space used to store an 1389 // ObjectElements instance.) 1390 // |MAX_DENSE_ELEMENTS_ALLOCATION * sizeof(JS::Value)| shouldn't overflow 1391 // int32_t (see elementsSizeMustNotOverflow). 1392 static const uint32_t MAX_DENSE_ELEMENTS_ALLOCATION = (1 << 28) - 1; 1393 1394 // The maximum number of usable dense elements in an object. 1395 static const uint32_t MAX_DENSE_ELEMENTS_COUNT = 1396 MAX_DENSE_ELEMENTS_ALLOCATION - ObjectElements::VALUES_PER_HEADER; 1397 1398 static void elementsSizeMustNotOverflow() { 1399 static_assert( 1400 NativeObject::MAX_DENSE_ELEMENTS_COUNT <= INT32_MAX / sizeof(JS::Value), 1401 "every caller of this method require that an element " 1402 "count multiplied by sizeof(Value) can't overflow " 1403 "uint32_t (and sometimes int32_t ,too)"); 1404 } 1405 1406 ObjectElements* getElementsHeader() const { 1407 return ObjectElements::fromElements(elements_); 1408 } 1409 1410 // Returns a pointer to the first element, including shifted elements. 1411 inline HeapSlot* unshiftedElements() const { 1412 return elements_ - getElementsHeader()->numShiftedElements(); 1413 } 1414 1415 // Like getElementsHeader, but returns a pointer to the unshifted header. 1416 // This is mainly useful for free()ing dynamic elements: the pointer 1417 // returned here is the one we got from malloc. 1418 void* getUnshiftedElementsHeader() const { 1419 return ObjectElements::fromElements(unshiftedElements()); 1420 } 1421 1422 uint32_t unshiftedIndex(uint32_t index) const { 1423 return index + getElementsHeader()->numShiftedElements(); 1424 } 1425 1426 /* Accessors for elements. */ 1427 bool ensureElements(JSContext* cx, uint32_t capacity) { 1428 MOZ_ASSERT(isExtensible()); 1429 if (capacity > getDenseCapacity()) { 1430 return growElements(cx, capacity); 1431 } 1432 return true; 1433 } 1434 1435 // Try to shift |count| dense elements, see the "Shifted elements" comment. 1436 inline bool tryShiftDenseElements(uint32_t count); 1437 1438 // Try to make space for |count| dense elements at the start of the array. 1439 bool tryUnshiftDenseElements(uint32_t count); 1440 1441 // Move the elements header and all shifted elements to the start of the 1442 // allocated elements space, so that numShiftedElements is 0 afterwards. 1443 void moveShiftedElements(); 1444 1445 // If this object has many shifted elements call moveShiftedElements. 1446 void maybeMoveShiftedElements(); 1447 1448 static bool goodElementsAllocationAmount(JSContext* cx, uint32_t reqAllocated, 1449 uint32_t length, 1450 uint32_t* goodAmount); 1451 bool growElements(JSContext* cx, uint32_t newcap); 1452 void shrinkElements(JSContext* cx, uint32_t cap); 1453 1454 private: 1455 // Run a post write barrier that encompasses multiple contiguous elements in a 1456 // single step. 1457 inline void elementsRangePostWriteBarrier(uint32_t start, uint32_t count); 1458 1459 public: 1460 void shrinkCapacityToInitializedLength(JSContext* cx); 1461 1462 private: 1463 void setDenseInitializedLengthInternal(uint32_t length) { 1464 MOZ_ASSERT(length <= getDenseCapacity()); 1465 MOZ_ASSERT(!denseElementsAreFrozen()); 1466 prepareElementRangeForOverwrite(length, 1467 getElementsHeader()->initializedLength); 1468 getElementsHeader()->initializedLength = length; 1469 } 1470 1471 public: 1472 void setDenseInitializedLength(uint32_t length) { 1473 MOZ_ASSERT(isExtensible()); 1474 setDenseInitializedLengthInternal(length); 1475 } 1476 1477 void setDenseInitializedLengthMaybeNonExtensible(JSContext* cx, 1478 uint32_t length) { 1479 setDenseInitializedLengthInternal(length); 1480 if (!isExtensible()) { 1481 shrinkCapacityToInitializedLength(cx); 1482 } 1483 } 1484 1485 inline void ensureDenseInitializedLength(uint32_t index, uint32_t extra); 1486 1487 void setDenseElement(uint32_t index, const Value& val) { 1488 MOZ_ASSERT_IF(val.isMagic(), val.whyMagic() != JS_ELEMENTS_HOLE); 1489 setDenseElementUnchecked(index, val); 1490 } 1491 1492 void initDenseElement(uint32_t index, const Value& val) { 1493 MOZ_ASSERT(!val.isMagic(JS_ELEMENTS_HOLE)); 1494 initDenseElementUnchecked(index, val); 1495 } 1496 1497 private: 1498 // Note: 'Unchecked' here means we don't assert |val| isn't the hole 1499 // MagicValue. 1500 void initDenseElementUnchecked(uint32_t index, const Value& val) { 1501 MOZ_ASSERT(index < getDenseInitializedLength()); 1502 MOZ_ASSERT(isExtensible()); 1503 checkStoredValue(val); 1504 elements_[index].init(this, HeapSlot::Element, unshiftedIndex(index), val); 1505 } 1506 void setDenseElementUnchecked(uint32_t index, const Value& val) { 1507 MOZ_ASSERT(index < getDenseInitializedLength()); 1508 MOZ_ASSERT(!denseElementsAreFrozen()); 1509 checkStoredValue(val); 1510 elements_[index].set(this, HeapSlot::Element, unshiftedIndex(index), val); 1511 } 1512 1513 // Mark the dense elements as possibly containing holes. 1514 inline void markDenseElementsNotPacked(); 1515 1516 public: 1517 inline void initDenseElementHole(uint32_t index); 1518 inline void setDenseElementHole(uint32_t index); 1519 inline void removeDenseElementForSparseIndex(uint32_t index); 1520 1521 inline void copyDenseElements(uint32_t dstStart, const Value* src, 1522 uint32_t count); 1523 1524 inline void initDenseElements(const Value* src, uint32_t count); 1525 inline void initDenseElements(IteratorProperty* src, uint32_t count); 1526 inline void initDenseElements(NativeObject* src, uint32_t srcStart, 1527 uint32_t count); 1528 1529 // Copy the first `count` dense elements from `src` to `this`, starting at 1530 // `destStart`. The initialized length must already include the new elements. 1531 inline void initDenseElementRange(uint32_t destStart, NativeObject* src, 1532 uint32_t count); 1533 1534 // Store the Values in the range [begin, end) as elements of this array. 1535 // 1536 // Preconditions: This must be a boring ArrayObject with dense initialized 1537 // length 0: no shifted elements, no frozen elements, no fixed "length", not 1538 // indexed, not inextensible, not copy-on-write. Existing capacity is 1539 // optional. 1540 // 1541 // This runs write barriers but does not update types. `end - begin` must 1542 // return the size of the range, which must be >= 0 and fit in an int32_t. 1543 template <typename Iter> 1544 [[nodiscard]] inline bool initDenseElementsFromRange(JSContext* cx, 1545 Iter begin, Iter end); 1546 1547 inline void moveDenseElements(uint32_t dstStart, uint32_t srcStart, 1548 uint32_t count); 1549 inline void reverseDenseElementsNoPreBarrier(uint32_t length); 1550 1551 inline DenseElementResult setOrExtendDenseElements(JSContext* cx, 1552 uint32_t start, 1553 const Value* vp, 1554 uint32_t count); 1555 1556 bool denseElementsAreSealed() const { 1557 return getElementsHeader()->isSealed(); 1558 } 1559 bool denseElementsAreFrozen() const { 1560 return hasFlag(ObjectFlag::FrozenElements); 1561 } 1562 1563 bool denseElementsArePacked() const { 1564 return getElementsHeader()->isPacked(); 1565 } 1566 1567 void markDenseElementsMaybeInIteration() { 1568 getElementsHeader()->markMaybeInIteration(); 1569 } 1570 1571 // Return whether the object's dense elements might be in the midst of for-in 1572 // iteration. We rely on this to be able to safely delete or move dense array 1573 // elements without worrying about updating in-progress iterators. 1574 // See bug 690622. 1575 // 1576 // Note that it's fine to return false if this object is on the prototype of 1577 // another object: SuppressDeletedProperty only suppresses properties deleted 1578 // from the iterated object itself. 1579 inline bool denseElementsHaveMaybeInIterationFlag(); 1580 inline bool denseElementsMaybeInIteration(); 1581 1582 // Ensures that the object can hold at least index + extra elements. This 1583 // returns DenseElement_Success on success, DenseElement_Failed on failure 1584 // to grow the array, or DenseElement_Incomplete when the object is too 1585 // sparse to grow (this includes the case of index + extra overflow). In 1586 // the last two cases the object is kept intact. 1587 inline DenseElementResult ensureDenseElements(JSContext* cx, uint32_t index, 1588 uint32_t extra); 1589 1590 inline DenseElementResult extendDenseElements(JSContext* cx, 1591 uint32_t requiredCapacity, 1592 uint32_t extra); 1593 1594 /* Small objects are dense, no matter what. */ 1595 static const uint32_t MIN_SPARSE_INDEX = 1000; 1596 1597 /* 1598 * Element storage for an object will be sparse if fewer than 1/8 indexes 1599 * are filled in. 1600 */ 1601 static const unsigned SPARSE_DENSITY_RATIO = 8; 1602 1603 /* 1604 * Check if after growing the object's elements will be too sparse. 1605 * newElementsHint is an estimated number of elements to be added. 1606 */ 1607 bool willBeSparseElements(uint32_t requiredCapacity, 1608 uint32_t newElementsHint); 1609 1610 /* 1611 * After adding a sparse index to obj, see if it should be converted to use 1612 * dense elements. 1613 */ 1614 static DenseElementResult maybeDensifySparseElements( 1615 JSContext* cx, Handle<NativeObject*> obj); 1616 static bool densifySparseElements(JSContext* cx, Handle<NativeObject*> obj); 1617 1618 inline HeapSlot* fixedElements() const { 1619 static_assert(2 * sizeof(Value) == sizeof(ObjectElements), 1620 "when elements are stored inline, the first two " 1621 "slots will hold the ObjectElements header"); 1622 return &fixedSlots()[2]; 1623 } 1624 1625 #ifdef DEBUG 1626 bool canHaveNonEmptyElements(); 1627 #endif 1628 1629 void setEmptyElements() { elements_ = emptyObjectElements; } 1630 1631 void initFixedElements(gc::AllocKind kind, uint32_t length); 1632 1633 // Update the elements pointer to use the fixed elements storage. The caller 1634 // is responsible for initializing the elements themselves and setting the 1635 // FIXED flag. 1636 void setFixedElements(uint32_t numShifted = 0) { 1637 MOZ_ASSERT(canHaveNonEmptyElements()); 1638 elements_ = fixedElements() + numShifted; 1639 } 1640 1641 inline bool hasDynamicElements() const { 1642 return !hasEmptyElements() && !hasFixedElements(); 1643 } 1644 1645 inline bool hasFixedElements() const { 1646 bool fixed = getElementsHeader()->flags & ObjectElements::FIXED; 1647 MOZ_ASSERT_IF(fixed, unshiftedElements() == fixedElements()); 1648 return fixed; 1649 } 1650 1651 inline bool hasEmptyElements() const { 1652 return elements_ == emptyObjectElements || 1653 elements_ == emptyObjectElementsShared; 1654 } 1655 1656 /* 1657 * Get a pointer to the unused data in the object's allocation immediately 1658 * following this object, for use with objects which allocate a larger size 1659 * class than they need and store non-elements data inline. 1660 */ 1661 inline uint8_t* fixedData(size_t nslots) const; 1662 1663 inline void privatePreWriteBarrier(HeapSlot* pprivate); 1664 1665 // The methods below are used to store GC things in a reserved slot as 1666 // PrivateValues. This is done to bypass the normal tracing code (debugger 1667 // objects use this to store cross-compartment pointers). 1668 // 1669 // WARNING: make sure you REALLY need this and you know what you're doing 1670 // before using these methods! 1671 void setReservedSlotGCThingAsPrivate(uint32_t slot, gc::Cell* cell) { 1672 #ifdef DEBUG 1673 if (IsMarkedBlack(this)) { 1674 JS::AssertCellIsNotGray(cell); 1675 } 1676 #endif 1677 HeapSlot* pslot = getSlotAddress(slot); 1678 Cell* prev = nullptr; 1679 if (!pslot->isUndefined()) { 1680 prev = static_cast<gc::Cell*>(pslot->toPrivate()); 1681 privatePreWriteBarrier(pslot); 1682 } 1683 setReservedSlotGCThingAsPrivateUnbarriered(slot, cell); 1684 gc::PostWriteBarrierCell(this, prev, cell); 1685 } 1686 void setReservedSlotGCThingAsPrivateUnbarriered(uint32_t slot, 1687 gc::Cell* cell) { 1688 MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(getClass())); 1689 MOZ_ASSERT(cell); 1690 getReservedSlotRef(slot).unbarrieredSet(PrivateValue(cell)); 1691 } 1692 void clearReservedSlotGCThingAsPrivate(uint32_t slot) { 1693 MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(getClass())); 1694 HeapSlot* pslot = &getReservedSlotRef(slot); 1695 if (!pslot->isUndefined()) { 1696 privatePreWriteBarrier(pslot); 1697 pslot->unbarrieredSet(UndefinedValue()); 1698 } 1699 } 1700 1701 // This is equivalent to |setReservedSlot(slot, PrivateValue(v))| but it 1702 // avoids GC barriers. Use this only when storing a private value in a 1703 // reserved slot that never holds a GC thing. 1704 void setReservedSlotPrivateUnbarriered(uint32_t slot, void* v) { 1705 MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(getClass())); 1706 MOZ_ASSERT(getReservedSlot(slot).isUndefined() || 1707 getReservedSlot(slot).isDouble()); 1708 getReservedSlotRef(slot).unbarrieredSet(PrivateValue(v)); 1709 } 1710 1711 // Like setReservedSlotPrivateUnbarriered but for PrivateUint32Value. 1712 void setReservedSlotPrivateUint32Unbarriered(uint32_t slot, uint32_t u) { 1713 MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(getClass())); 1714 MOZ_ASSERT(getReservedSlot(slot).isUndefined() || 1715 getReservedSlot(slot).isInt32()); 1716 getReservedSlotRef(slot).unbarrieredSet(PrivateUint32Value(u)); 1717 } 1718 1719 /* Return the allocKind we would use if we were to tenure this object. */ 1720 inline js::gc::AllocKind allocKindForTenure() const; 1721 1722 // Native objects are never wrappers, so a native object always has a realm 1723 // and global. 1724 JS::Realm* realm() const { return nonCCWRealm(); } 1725 inline js::GlobalObject& global() const; 1726 1727 TaggedSlotOffset getTaggedSlotOffset(size_t slot) const { 1728 MOZ_ASSERT(slot < slotSpan()); 1729 uint32_t nfixed = numFixedSlots(); 1730 if (slot < nfixed) { 1731 return TaggedSlotOffset(getFixedSlotOffset(slot), 1732 /* isFixedSlot = */ true); 1733 } 1734 return TaggedSlotOffset((slot - nfixed) * sizeof(Value), 1735 /* isFixedSlot = */ false); 1736 } 1737 1738 bool hasUnpreservedWrapper() const { 1739 return getClass()->preservesWrapper() && 1740 !shape()->hasObjectFlag(ObjectFlag::HasPreservedWrapper); 1741 } 1742 1743 /* JIT Accessors */ 1744 static size_t offsetOfElements() { return offsetof(NativeObject, elements_); } 1745 static size_t offsetOfFixedElements() { 1746 return sizeof(NativeObject) + sizeof(ObjectElements); 1747 } 1748 1749 static constexpr size_t getFixedSlotOffset(size_t slot) { 1750 MOZ_ASSERT(slot < MAX_FIXED_SLOTS); 1751 return sizeof(NativeObject) + slot * sizeof(Value); 1752 } 1753 static constexpr size_t getFixedSlotIndexFromOffset(size_t offset) { 1754 MOZ_ASSERT(offset >= sizeof(NativeObject)); 1755 offset -= sizeof(NativeObject); 1756 MOZ_ASSERT(offset % sizeof(Value) == 0); 1757 MOZ_ASSERT(offset / sizeof(Value) < MAX_FIXED_SLOTS); 1758 return offset / sizeof(Value); 1759 } 1760 static constexpr size_t getDynamicSlotIndexFromOffset(size_t offset) { 1761 MOZ_ASSERT(offset % sizeof(Value) == 0); 1762 return offset / sizeof(Value); 1763 } 1764 static size_t offsetOfSlots() { return offsetof(NativeObject, slots_); } 1765 }; 1766 1767 inline void NativeObject::privatePreWriteBarrier(HeapSlot* pprivate) { 1768 JS::shadow::Zone* shadowZone = this->shadowZoneFromAnyThread(); 1769 if (shadowZone->needsIncrementalBarrier() && pprivate->get().toPrivate() && 1770 getClass()->hasTrace()) { 1771 getClass()->doTrace(shadowZone->barrierTracer(), this); 1772 } 1773 } 1774 1775 /*** Standard internal methods **********************************************/ 1776 1777 /* 1778 * These functions should follow the algorithms in ES2025 section 10.1 1779 * ("Ordinary Object Internal Methods"). 1780 * 1781 * Many native objects are not "ordinary" in ES2025, so these functions also 1782 * have to serve some of the special needs of Arrays (10.4.2), Strings (10.4.3), 1783 * and so on. 1784 */ 1785 1786 extern bool NativeDefineProperty(JSContext* cx, Handle<NativeObject*> obj, 1787 HandleId id, 1788 Handle<JS::PropertyDescriptor> desc, 1789 ObjectOpResult& result); 1790 1791 extern bool NativeDefineDataProperty(JSContext* cx, Handle<NativeObject*> obj, 1792 HandleId id, HandleValue value, 1793 unsigned attrs, ObjectOpResult& result); 1794 1795 /* If the result out-param is omitted, throw on failure. */ 1796 1797 extern bool NativeDefineAccessorProperty(JSContext* cx, 1798 Handle<NativeObject*> obj, HandleId id, 1799 HandleObject getter, 1800 HandleObject setter, unsigned attrs); 1801 1802 extern bool NativeDefineDataProperty(JSContext* cx, Handle<NativeObject*> obj, 1803 HandleId id, HandleValue value, 1804 unsigned attrs); 1805 1806 extern bool NativeDefineDataProperty(JSContext* cx, Handle<NativeObject*> obj, 1807 PropertyName* name, HandleValue value, 1808 unsigned attrs); 1809 1810 extern bool NativeHasProperty(JSContext* cx, Handle<NativeObject*> obj, 1811 HandleId id, bool* foundp); 1812 1813 extern bool NativeGetOwnPropertyDescriptor( 1814 JSContext* cx, Handle<NativeObject*> obj, HandleId id, 1815 MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc); 1816 1817 extern bool NativeGetProperty(JSContext* cx, Handle<NativeObject*> obj, 1818 HandleValue receiver, HandleId id, 1819 MutableHandleValue vp); 1820 1821 extern bool NativeGetPropertyNoGC(JSContext* cx, NativeObject* obj, 1822 const Value& receiver, jsid id, Value* vp); 1823 1824 inline bool NativeGetProperty(JSContext* cx, Handle<NativeObject*> obj, 1825 HandleId id, MutableHandleValue vp) { 1826 RootedValue receiver(cx, ObjectValue(*obj)); 1827 return NativeGetProperty(cx, obj, receiver, id, vp); 1828 } 1829 1830 extern bool NativeGetElement(JSContext* cx, Handle<NativeObject*> obj, 1831 HandleValue receiver, int32_t index, 1832 MutableHandleValue vp); 1833 1834 bool GetSparseElementHelper(JSContext* cx, Handle<NativeObject*> obj, 1835 int32_t int_id, MutableHandleValue result); 1836 1837 bool AddOrUpdateSparseElementHelper(JSContext* cx, Handle<NativeObject*> obj, 1838 int32_t int_id, HandleValue v, bool strict); 1839 1840 /* 1841 * Indicates whether an assignment operation is qualified (`x.y = 0`) or 1842 * unqualified (`y = 0`). In strict mode, the latter is an error if no such 1843 * variable already exists. 1844 * 1845 * Used as an argument to NativeSetProperty. 1846 */ 1847 enum QualifiedBool { Unqualified = 0, Qualified = 1 }; 1848 1849 template <QualifiedBool Qualified> 1850 extern bool NativeSetProperty(JSContext* cx, Handle<NativeObject*> obj, 1851 HandleId id, HandleValue v, HandleValue receiver, 1852 ObjectOpResult& result); 1853 1854 extern bool NativeSetElement(JSContext* cx, Handle<NativeObject*> obj, 1855 uint32_t index, HandleValue v, 1856 HandleValue receiver, ObjectOpResult& result); 1857 1858 extern bool NativeDeleteProperty(JSContext* cx, Handle<NativeObject*> obj, 1859 HandleId id, ObjectOpResult& result); 1860 1861 /*** SpiderMonkey nonstandard internal methods ******************************/ 1862 1863 template <AllowGC allowGC> 1864 extern bool NativeLookupOwnProperty( 1865 JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, 1866 typename MaybeRooted<jsid, allowGC>::HandleType id, PropertyResult* propp); 1867 1868 /* 1869 * Get a property from `receiver`, after having already done a lookup and found 1870 * the property on a native object `obj`. 1871 * 1872 * `prop` must be present in obj's shape. 1873 */ 1874 extern bool NativeGetExistingProperty(JSContext* cx, HandleObject receiver, 1875 Handle<NativeObject*> obj, HandleId id, 1876 PropertyInfo prop, MutableHandleValue vp); 1877 1878 /* * */ 1879 1880 extern bool GetNameBoundInEnvironment(JSContext* cx, HandleObject env, 1881 HandleId id, MutableHandleValue vp); 1882 1883 } /* namespace js */ 1884 1885 template <> 1886 inline bool JSObject::is<js::NativeObject>() const { 1887 return shape()->isNative(); 1888 } 1889 1890 namespace js { 1891 1892 // Alternate to JSObject::as<NativeObject>() that tolerates null pointers. 1893 inline NativeObject* MaybeNativeObject(JSObject* obj) { 1894 return obj ? &obj->as<NativeObject>() : nullptr; 1895 } 1896 1897 // Defined in NativeObject-inl.h. 1898 bool IsPackedArray(JSObject* obj); 1899 1900 // Initialize an object's reserved slot with a private value pointing to 1901 // malloc-allocated memory and associate the memory with the object. 1902 // 1903 // This call should be matched with a call to JS::GCContext::free_/delete_ in 1904 // the object's finalizer to free the memory and update the memory accounting. 1905 1906 inline void InitReservedSlot(NativeObject* obj, uint32_t slot, void* ptr, 1907 size_t nbytes, MemoryUse use) { 1908 AddCellMemory(obj, nbytes, use); 1909 obj->initReservedSlot(slot, PrivateValue(ptr)); 1910 } 1911 template <typename T> 1912 inline void InitReservedSlot(NativeObject* obj, uint32_t slot, T* ptr, 1913 MemoryUse use) { 1914 InitReservedSlot(obj, slot, ptr, sizeof(T), use); 1915 } 1916 1917 bool AddSlotAndCallAddPropHook(JSContext* cx, Handle<NativeObject*> obj, 1918 HandleValue v, Handle<Shape*> newShape); 1919 1920 } // namespace js 1921 1922 #endif /* vm_NativeObject_h */