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 "vm/NativeObject-inl.h"
8
9 #include "mozilla/Casting.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/DebugOnly.h"
12 #include "mozilla/Maybe.h"
13
14 #include <algorithm>
15 #include <iterator>
16
17 #include "debugger/DebugAPI.h"
18 #include "gc/Marking.h"
19 #include "gc/MaybeRooted.h"
20 #include "jit/BaselineIC.h"
21 #include "js/CharacterEncoding.h"
22 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
23 #include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
24 #include "js/Result.h"
25 #include "js/Value.h"
26 #include "util/Memory.h"
27 #include "vm/EqualityOperations.h" // js::SameValue
28 #include "vm/GetterSetter.h" // js::GetterSetter
29 #include "vm/PlainObject.h" // js::PlainObject
30 #include "vm/TypedArrayObject.h"
31
32 #include "gc/Nursery-inl.h"
33 #include "vm/ArrayObject-inl.h"
34 #include "vm/BytecodeLocation-inl.h"
35 #include "vm/EnvironmentObject-inl.h"
36 #include "vm/JSObject-inl.h"
37 #include "vm/JSScript-inl.h"
38 #include "vm/Shape-inl.h"
39
40 using namespace js;
41
42 using JS::AutoCheckCannotGC;
43 using mozilla::CheckedInt;
44 using mozilla::DebugOnly;
45 using mozilla::PodCopy;
46 using mozilla::RoundUpPow2;
47
48 struct EmptyObjectElements {
49 const ObjectElements emptyElementsHeader;
50
51 // Add an extra (unused) Value to make sure an out-of-bounds index when
52 // masked (resulting in index 0) accesses valid memory.
53 const Value val;
54
55 public:
EmptyObjectElementsEmptyObjectElements56 constexpr EmptyObjectElements()
57 : emptyElementsHeader(0, 0), val(UndefinedValue()) {}
EmptyObjectElementsEmptyObjectElements58 explicit constexpr EmptyObjectElements(ObjectElements::SharedMemory shmem)
59 : emptyElementsHeader(0, 0, shmem), val(UndefinedValue()) {}
60 };
61
62 static constexpr EmptyObjectElements emptyElementsHeader;
63
64 /* Objects with no elements share one empty set of elements. */
65 HeapSlot* const js::emptyObjectElements = reinterpret_cast<HeapSlot*>(
66 uintptr_t(&emptyElementsHeader) + sizeof(ObjectElements));
67
68 static constexpr EmptyObjectElements emptyElementsHeaderShared(
69 ObjectElements::SharedMemory::IsShared);
70
71 /* Objects with no elements share one empty set of elements. */
72 HeapSlot* const js::emptyObjectElementsShared = reinterpret_cast<HeapSlot*>(
73 uintptr_t(&emptyElementsHeaderShared) + sizeof(ObjectElements));
74
75 struct EmptyObjectSlots : public ObjectSlots {
EmptyObjectSlotsEmptyObjectSlots76 explicit constexpr EmptyObjectSlots(size_t dictionarySlotSpan)
77 : ObjectSlots(0, dictionarySlotSpan) {}
78 };
79
80 static constexpr EmptyObjectSlots emptyObjectSlotsHeaders[17] = {
81 EmptyObjectSlots(0), EmptyObjectSlots(1), EmptyObjectSlots(2),
82 EmptyObjectSlots(3), EmptyObjectSlots(4), EmptyObjectSlots(5),
83 EmptyObjectSlots(6), EmptyObjectSlots(7), EmptyObjectSlots(8),
84 EmptyObjectSlots(9), EmptyObjectSlots(10), EmptyObjectSlots(11),
85 EmptyObjectSlots(12), EmptyObjectSlots(13), EmptyObjectSlots(14),
86 EmptyObjectSlots(15), EmptyObjectSlots(16)};
87
88 static_assert(std::size(emptyObjectSlotsHeaders) ==
89 NativeObject::MAX_FIXED_SLOTS + 1);
90
91 HeapSlot* const js::emptyObjectSlotsForDictionaryObject[17] = {
92 emptyObjectSlotsHeaders[0].slots(), emptyObjectSlotsHeaders[1].slots(),
93 emptyObjectSlotsHeaders[2].slots(), emptyObjectSlotsHeaders[3].slots(),
94 emptyObjectSlotsHeaders[4].slots(), emptyObjectSlotsHeaders[5].slots(),
95 emptyObjectSlotsHeaders[6].slots(), emptyObjectSlotsHeaders[7].slots(),
96 emptyObjectSlotsHeaders[8].slots(), emptyObjectSlotsHeaders[9].slots(),
97 emptyObjectSlotsHeaders[10].slots(), emptyObjectSlotsHeaders[11].slots(),
98 emptyObjectSlotsHeaders[12].slots(), emptyObjectSlotsHeaders[13].slots(),
99 emptyObjectSlotsHeaders[14].slots(), emptyObjectSlotsHeaders[15].slots(),
100 emptyObjectSlotsHeaders[16].slots()};
101
102 static_assert(std::size(emptyObjectSlotsForDictionaryObject) ==
103 NativeObject::MAX_FIXED_SLOTS + 1);
104
105 HeapSlot* const js::emptyObjectSlots = emptyObjectSlotsForDictionaryObject[0];
106
107 #ifdef DEBUG
108
canHaveNonEmptyElements()109 bool NativeObject::canHaveNonEmptyElements() {
110 return !this->is<TypedArrayObject>();
111 }
112
113 #endif // DEBUG
114
115 /* static */
PrepareForPreventExtensions(JSContext * cx,NativeObject * obj)116 void ObjectElements::PrepareForPreventExtensions(JSContext* cx,
117 NativeObject* obj) {
118 if (!obj->hasEmptyElements()) {
119 obj->shrinkCapacityToInitializedLength(cx);
120 }
121
122 // shrinkCapacityToInitializedLength ensures there are no shifted elements.
123 MOZ_ASSERT(obj->getElementsHeader()->numShiftedElements() == 0);
124 }
125
126 /* static */
PreventExtensions(NativeObject * obj)127 void ObjectElements::PreventExtensions(NativeObject* obj) {
128 MOZ_ASSERT(!obj->isExtensible());
129 MOZ_ASSERT(obj->getElementsHeader()->numShiftedElements() == 0);
130 MOZ_ASSERT(obj->getDenseInitializedLength() == obj->getDenseCapacity());
131
132 if (!obj->hasEmptyElements()) {
133 obj->getElementsHeader()->setNotExtensible();
134 }
135 }
136
137 /* static */
FreezeOrSeal(JSContext * cx,HandleNativeObject obj,IntegrityLevel level)138 bool ObjectElements::FreezeOrSeal(JSContext* cx, HandleNativeObject obj,
139 IntegrityLevel level) {
140 MOZ_ASSERT_IF(level == IntegrityLevel::Frozen && obj->is<ArrayObject>(),
141 !obj->as<ArrayObject>().lengthIsWritable());
142 MOZ_ASSERT(!obj->isExtensible());
143 MOZ_ASSERT(obj->getElementsHeader()->numShiftedElements() == 0);
144
145 if (obj->hasEmptyElements() || obj->denseElementsAreFrozen()) {
146 return true;
147 }
148
149 if (level == IntegrityLevel::Frozen) {
150 if (!JSObject::setFlag(cx, obj, ObjectFlag::FrozenElements)) {
151 return false;
152 }
153 }
154
155 if (!obj->denseElementsAreSealed()) {
156 obj->getElementsHeader()->seal();
157 }
158
159 if (level == IntegrityLevel::Frozen) {
160 obj->getElementsHeader()->freeze();
161 }
162
163 return true;
164 }
165
166 #ifdef DEBUG
167 static mozilla::Atomic<bool, mozilla::Relaxed> gShapeConsistencyChecksEnabled(
168 false);
169
170 /* static */
enableShapeConsistencyChecks()171 void js::NativeObject::enableShapeConsistencyChecks() {
172 gShapeConsistencyChecksEnabled = true;
173 }
174
checkShapeConsistency()175 void js::NativeObject::checkShapeConsistency() {
176 if (!gShapeConsistencyChecksEnabled) {
177 return;
178 }
179
180 MOZ_ASSERT(is<NativeObject>());
181
182 if (PropMap* map = shape()->propMap()) {
183 map->checkConsistency(this);
184 } else {
185 MOZ_ASSERT(shape()->propMapLength() == 0);
186 }
187 }
188 #endif
189
initializeSlotRange(uint32_t start,uint32_t end)190 void js::NativeObject::initializeSlotRange(uint32_t start, uint32_t end) {
191 /*
192 * No bounds check, as this is used when the object's shape does not
193 * reflect its allocated slots (updateSlotsForSpan).
194 */
195 HeapSlot* fixedStart;
196 HeapSlot* fixedEnd;
197 HeapSlot* slotsStart;
198 HeapSlot* slotsEnd;
199 getSlotRangeUnchecked(start, end, &fixedStart, &fixedEnd, &slotsStart,
200 &slotsEnd);
201
202 uint32_t offset = start;
203 for (HeapSlot* sp = fixedStart; sp < fixedEnd; sp++) {
204 sp->init(this, HeapSlot::Slot, offset++, UndefinedValue());
205 }
206 for (HeapSlot* sp = slotsStart; sp < slotsEnd; sp++) {
207 sp->init(this, HeapSlot::Slot, offset++, UndefinedValue());
208 }
209 }
210
initSlots(const Value * vector,uint32_t length)211 void js::NativeObject::initSlots(const Value* vector, uint32_t length) {
212 HeapSlot* fixedStart;
213 HeapSlot* fixedEnd;
214 HeapSlot* slotsStart;
215 HeapSlot* slotsEnd;
216 getSlotRange(0, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
217
218 uint32_t offset = 0;
219 for (HeapSlot* sp = fixedStart; sp < fixedEnd; sp++) {
220 sp->init(this, HeapSlot::Slot, offset++, *vector++);
221 }
222 for (HeapSlot* sp = slotsStart; sp < slotsEnd; sp++) {
223 sp->init(this, HeapSlot::Slot, offset++, *vector++);
224 }
225 }
226
227 #ifdef DEBUG
228
slotInRange(uint32_t slot,SentinelAllowed sentinel) const229 bool js::NativeObject::slotInRange(uint32_t slot,
230 SentinelAllowed sentinel) const {
231 MOZ_ASSERT(!gc::IsForwarded(shape()));
232 uint32_t capacity = numFixedSlots() + numDynamicSlots();
233 if (sentinel == SENTINEL_ALLOWED) {
234 return slot <= capacity;
235 }
236 return slot < capacity;
237 }
238
slotIsFixed(uint32_t slot) const239 bool js::NativeObject::slotIsFixed(uint32_t slot) const {
240 // We call numFixedSlotsMaybeForwarded() to allow reading slots of
241 // associated objects in trace hooks that may be called during a moving GC.
242 return slot < numFixedSlotsMaybeForwarded();
243 }
244
isNumFixedSlots(uint32_t nfixed) const245 bool js::NativeObject::isNumFixedSlots(uint32_t nfixed) const {
246 // We call numFixedSlotsMaybeForwarded() to allow reading slots of
247 // associated objects in trace hooks that may be called during a moving GC.
248 return nfixed == numFixedSlotsMaybeForwarded();
249 }
250
251 #endif /* DEBUG */
252
lookup(JSContext * cx,jsid id)253 mozilla::Maybe<PropertyInfo> js::NativeObject::lookup(JSContext* cx, jsid id) {
254 MOZ_ASSERT(is<NativeObject>());
255 uint32_t index;
256 if (PropMap* map = shape()->lookup(cx, id, &index)) {
257 return mozilla::Some(map->getPropertyInfo(index));
258 }
259 return mozilla::Nothing();
260 }
261
lookupPure(jsid id)262 mozilla::Maybe<PropertyInfo> js::NativeObject::lookupPure(jsid id) {
263 MOZ_ASSERT(is<NativeObject>());
264 uint32_t index;
265 if (PropMap* map = shape()->lookupPure(id, &index)) {
266 return mozilla::Some(map->getPropertyInfo(index));
267 }
268 return mozilla::Nothing();
269 }
270
ensureSlotsForDictionaryObject(JSContext * cx,uint32_t span)271 bool NativeObject::ensureSlotsForDictionaryObject(JSContext* cx,
272 uint32_t span) {
273 MOZ_ASSERT(inDictionaryMode());
274
275 size_t oldSpan = dictionaryModeSlotSpan();
276 if (oldSpan == span) {
277 return true;
278 }
279
280 if (!updateSlotsForSpan(cx, oldSpan, span)) {
281 return false;
282 }
283
284 setDictionaryModeSlotSpan(span);
285 return true;
286 }
287
growSlots(JSContext * cx,uint32_t oldCapacity,uint32_t newCapacity)288 bool NativeObject::growSlots(JSContext* cx, uint32_t oldCapacity,
289 uint32_t newCapacity) {
290 MOZ_ASSERT(newCapacity > oldCapacity);
291 MOZ_ASSERT_IF(!is<ArrayObject>(), newCapacity >= SLOT_CAPACITY_MIN);
292
293 /*
294 * Slot capacities are determined by the span of allocated objects. Due to
295 * the limited number of bits to store shape slots, object growth is
296 * throttled well before the slot capacity can overflow.
297 */
298 NativeObject::slotsSizeMustNotOverflow();
299 MOZ_ASSERT(newCapacity <= MAX_SLOTS_COUNT);
300
301 if (!hasDynamicSlots()) {
302 return allocateSlots(cx, newCapacity);
303 }
304
305 uint32_t newAllocated = ObjectSlots::allocCount(newCapacity);
306
307 uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan();
308
309 uint32_t oldAllocated = ObjectSlots::allocCount(oldCapacity);
310
311 ObjectSlots* oldHeaderSlots = ObjectSlots::fromSlots(slots_);
312 MOZ_ASSERT(oldHeaderSlots->capacity() == oldCapacity);
313
314 HeapSlot* allocation = ReallocateObjectBuffer<HeapSlot>(
315 cx, this, reinterpret_cast<HeapSlot*>(oldHeaderSlots), oldAllocated,
316 newAllocated);
317 if (!allocation) {
318 return false; /* Leave slots at its old size. */
319 }
320
321 auto* newHeaderSlots =
322 new (allocation) ObjectSlots(newCapacity, dictionarySpan);
323 slots_ = newHeaderSlots->slots();
324
325 Debug_SetSlotRangeToCrashOnTouch(slots_ + oldCapacity,
326 newCapacity - oldCapacity);
327
328 RemoveCellMemory(this, ObjectSlots::allocSize(oldCapacity),
329 MemoryUse::ObjectSlots);
330 AddCellMemory(this, ObjectSlots::allocSize(newCapacity),
331 MemoryUse::ObjectSlots);
332
333 MOZ_ASSERT(hasDynamicSlots());
334 return true;
335 }
336
allocateSlots(JSContext * cx,uint32_t newCapacity)337 bool NativeObject::allocateSlots(JSContext* cx, uint32_t newCapacity) {
338 MOZ_ASSERT(!hasDynamicSlots());
339
340 uint32_t newAllocated = ObjectSlots::allocCount(newCapacity);
341
342 uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan();
343
344 HeapSlot* allocation = AllocateObjectBuffer<HeapSlot>(cx, this, newAllocated);
345 if (!allocation) {
346 return false;
347 }
348
349 auto* newHeaderSlots =
350 new (allocation) ObjectSlots(newCapacity, dictionarySpan);
351 slots_ = newHeaderSlots->slots();
352
353 Debug_SetSlotRangeToCrashOnTouch(slots_, newCapacity);
354
355 AddCellMemory(this, ObjectSlots::allocSize(newCapacity),
356 MemoryUse::ObjectSlots);
357
358 MOZ_ASSERT(hasDynamicSlots());
359 return true;
360 }
361
362 /* static */
growSlotsPure(JSContext * cx,NativeObject * obj,uint32_t newCapacity)363 bool NativeObject::growSlotsPure(JSContext* cx, NativeObject* obj,
364 uint32_t newCapacity) {
365 // IC code calls this directly.
366 AutoUnsafeCallWithABI unsafe;
367
368 if (!obj->growSlots(cx, obj->numDynamicSlots(), newCapacity)) {
369 cx->recoverFromOutOfMemory();
370 return false;
371 }
372
373 return true;
374 }
375
376 /* static */
addDenseElementPure(JSContext * cx,NativeObject * obj)377 bool NativeObject::addDenseElementPure(JSContext* cx, NativeObject* obj) {
378 // IC code calls this directly.
379 AutoUnsafeCallWithABI unsafe;
380
381 MOZ_ASSERT(obj->getDenseInitializedLength() == obj->getDenseCapacity());
382 MOZ_ASSERT(obj->isExtensible());
383 MOZ_ASSERT(!obj->isIndexed());
384 MOZ_ASSERT(!obj->is<TypedArrayObject>());
385 MOZ_ASSERT_IF(obj->is<ArrayObject>(),
386 obj->as<ArrayObject>().lengthIsWritable());
387
388 // growElements will report OOM also if the number of dense elements will
389 // exceed MAX_DENSE_ELEMENTS_COUNT. See goodElementsAllocationAmount.
390 uint32_t oldCapacity = obj->getDenseCapacity();
391 if (MOZ_UNLIKELY(!obj->growElements(cx, oldCapacity + 1))) {
392 cx->recoverFromOutOfMemory();
393 return false;
394 }
395
396 MOZ_ASSERT(obj->getDenseCapacity() > oldCapacity);
397 MOZ_ASSERT(obj->getDenseCapacity() <= MAX_DENSE_ELEMENTS_COUNT);
398 return true;
399 }
400
FreeSlots(JSContext * cx,NativeObject * obj,ObjectSlots * slots,size_t nbytes)401 static inline void FreeSlots(JSContext* cx, NativeObject* obj,
402 ObjectSlots* slots, size_t nbytes) {
403 if (cx->isHelperThreadContext()) {
404 js_free(slots);
405 } else if (obj->isTenured()) {
406 MOZ_ASSERT(!cx->nursery().isInside(slots));
407 js_free(slots);
408 } else {
409 cx->nursery().freeBuffer(slots, nbytes);
410 }
411 }
412
shrinkSlots(JSContext * cx,uint32_t oldCapacity,uint32_t newCapacity)413 void NativeObject::shrinkSlots(JSContext* cx, uint32_t oldCapacity,
414 uint32_t newCapacity) {
415 MOZ_ASSERT(newCapacity < oldCapacity);
416 MOZ_ASSERT(oldCapacity == getSlotsHeader()->capacity());
417
418 uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan();
419
420 ObjectSlots* oldHeaderSlots = ObjectSlots::fromSlots(slots_);
421 MOZ_ASSERT(oldHeaderSlots->capacity() == oldCapacity);
422
423 uint32_t oldAllocated = ObjectSlots::allocCount(oldCapacity);
424
425 if (newCapacity == 0) {
426 size_t nbytes = ObjectSlots::allocSize(oldCapacity);
427 RemoveCellMemory(this, nbytes, MemoryUse::ObjectSlots);
428 FreeSlots(cx, this, oldHeaderSlots, nbytes);
429 setEmptyDynamicSlots(dictionarySpan);
430 return;
431 }
432
433 MOZ_ASSERT_IF(!is<ArrayObject>(), newCapacity >= SLOT_CAPACITY_MIN);
434
435 uint32_t newAllocated = ObjectSlots::allocCount(newCapacity);
436
437 HeapSlot* allocation = ReallocateObjectBuffer<HeapSlot>(
438 cx, this, reinterpret_cast<HeapSlot*>(oldHeaderSlots), oldAllocated,
439 newAllocated);
440 if (!allocation) {
441 // It's possible for realloc to fail when shrinking an allocation. In this
442 // case we continue using the original allocation but still update the
443 // capacity to the new requested capacity, which is smaller than the actual
444 // capacity.
445 cx->recoverFromOutOfMemory();
446 allocation = reinterpret_cast<HeapSlot*>(getSlotsHeader());
447 }
448
449 RemoveCellMemory(this, ObjectSlots::allocSize(oldCapacity),
450 MemoryUse::ObjectSlots);
451 AddCellMemory(this, ObjectSlots::allocSize(newCapacity),
452 MemoryUse::ObjectSlots);
453
454 auto* newHeaderSlots =
455 new (allocation) ObjectSlots(newCapacity, dictionarySpan);
456 slots_ = newHeaderSlots->slots();
457 }
458
willBeSparseElements(uint32_t requiredCapacity,uint32_t newElementsHint)459 bool NativeObject::willBeSparseElements(uint32_t requiredCapacity,
460 uint32_t newElementsHint) {
461 MOZ_ASSERT(is<NativeObject>());
462 MOZ_ASSERT(requiredCapacity > MIN_SPARSE_INDEX);
463
464 uint32_t cap = getDenseCapacity();
465 MOZ_ASSERT(requiredCapacity >= cap);
466
467 if (requiredCapacity > MAX_DENSE_ELEMENTS_COUNT) {
468 return true;
469 }
470
471 uint32_t minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO;
472 if (newElementsHint >= minimalDenseCount) {
473 return false;
474 }
475 minimalDenseCount -= newElementsHint;
476
477 if (minimalDenseCount > cap) {
478 return true;
479 }
480
481 uint32_t len = getDenseInitializedLength();
482 const Value* elems = getDenseElements();
483 for (uint32_t i = 0; i < len; i++) {
484 if (!elems[i].isMagic(JS_ELEMENTS_HOLE) && !--minimalDenseCount) {
485 return false;
486 }
487 }
488 return true;
489 }
490
491 /* static */
maybeDensifySparseElements(JSContext * cx,HandleNativeObject obj)492 DenseElementResult NativeObject::maybeDensifySparseElements(
493 JSContext* cx, HandleNativeObject obj) {
494 /*
495 * Wait until after the object goes into dictionary mode, which must happen
496 * when sparsely packing any array with more than MIN_SPARSE_INDEX elements
497 * (see PropertyTree::MAX_HEIGHT).
498 */
499 if (!obj->inDictionaryMode()) {
500 return DenseElementResult::Incomplete;
501 }
502
503 /*
504 * Only measure the number of indexed properties every log(n) times when
505 * populating the object.
506 */
507 uint32_t slotSpan = obj->slotSpan();
508 if (slotSpan != RoundUpPow2(slotSpan)) {
509 return DenseElementResult::Incomplete;
510 }
511
512 /* Watch for conditions under which an object's elements cannot be dense. */
513 if (!obj->isExtensible()) {
514 return DenseElementResult::Incomplete;
515 }
516
517 /*
518 * The indexes in the object need to be sufficiently dense before they can
519 * be converted to dense mode.
520 */
521 uint32_t numDenseElements = 0;
522 uint32_t newInitializedLength = 0;
523
524 for (ShapePropertyIter<NoGC> iter(obj->shape()); !iter.done(); iter++) {
525 uint32_t index;
526 if (!IdIsIndex(iter->key(), &index)) {
527 continue;
528 }
529 if (iter->flags() != PropertyFlags::defaultDataPropFlags) {
530 // For simplicity, only densify the object if all indexed properties can
531 // be converted to dense elements.
532 return DenseElementResult::Incomplete;
533 }
534 MOZ_ASSERT(iter->isDataProperty());
535 numDenseElements++;
536 newInitializedLength = std::max(newInitializedLength, index + 1);
537 }
538
539 if (numDenseElements * SPARSE_DENSITY_RATIO < newInitializedLength) {
540 return DenseElementResult::Incomplete;
541 }
542
543 if (newInitializedLength > MAX_DENSE_ELEMENTS_COUNT) {
544 return DenseElementResult::Incomplete;
545 }
546
547 /*
548 * This object meets all necessary restrictions, convert all indexed
549 * properties into dense elements.
550 */
551
552 if (newInitializedLength > obj->getDenseCapacity()) {
553 if (!obj->growElements(cx, newInitializedLength)) {
554 return DenseElementResult::Failure;
555 }
556 }
557
558 obj->ensureDenseInitializedLength(newInitializedLength, 0);
559
560 if (ObjectRealm::get(obj).objectMaybeInIteration(obj)) {
561 // Mark the densified elements as maybe-in-iteration. See also the comment
562 // in GetIterator.
563 obj->markDenseElementsMaybeInIteration();
564 }
565
566 if (!NativeObject::densifySparseElements(cx, obj)) {
567 return DenseElementResult::Failure;
568 }
569
570 return DenseElementResult::Success;
571 }
572
moveShiftedElements()573 void NativeObject::moveShiftedElements() {
574 MOZ_ASSERT(isExtensible());
575
576 ObjectElements* header = getElementsHeader();
577 uint32_t numShifted = header->numShiftedElements();
578 MOZ_ASSERT(numShifted > 0);
579
580 uint32_t initLength = header->initializedLength;
581
582 ObjectElements* newHeader =
583 static_cast<ObjectElements*>(getUnshiftedElementsHeader());
584 memmove(newHeader, header, sizeof(ObjectElements));
585
586 newHeader->clearShiftedElements();
587 newHeader->capacity += numShifted;
588 elements_ = newHeader->elements();
589
590 // To move the elements, temporarily update initializedLength to include
591 // the shifted elements.
592 newHeader->initializedLength += numShifted;
593
594 // Move the elements. Initialize to |undefined| to ensure pre-barriers
595 // don't see garbage.
596 for (size_t i = 0; i < numShifted; i++) {
597 initDenseElement(i, UndefinedValue());
598 }
599 moveDenseElements(0, numShifted, initLength);
600
601 // Restore the initialized length. We use setDenseInitializedLength to
602 // make sure prepareElementRangeForOverwrite is called on the shifted
603 // elements.
604 setDenseInitializedLength(initLength);
605 }
606
maybeMoveShiftedElements()607 void NativeObject::maybeMoveShiftedElements() {
608 MOZ_ASSERT(isExtensible());
609
610 ObjectElements* header = getElementsHeader();
611 MOZ_ASSERT(header->numShiftedElements() > 0);
612
613 // Move the elements if less than a third of the allocated space is in use.
614 if (header->capacity < header->numAllocatedElements() / 3) {
615 moveShiftedElements();
616 }
617 }
618
tryUnshiftDenseElements(uint32_t count)619 bool NativeObject::tryUnshiftDenseElements(uint32_t count) {
620 MOZ_ASSERT(isExtensible());
621 MOZ_ASSERT(count > 0);
622
623 ObjectElements* header = getElementsHeader();
624 uint32_t numShifted = header->numShiftedElements();
625
626 if (count > numShifted) {
627 // We need more elements than are easily available. Try to make space
628 // for more elements than we need (and shift the remaining ones) so
629 // that unshifting more elements later will be fast.
630
631 // Don't bother reserving elements if the number of elements is small.
632 // Note that there's no technical reason for using this particular
633 // limit.
634 if (header->initializedLength <= 10 ||
635 header->hasNonwritableArrayLength() ||
636 MOZ_UNLIKELY(count > ObjectElements::MaxShiftedElements)) {
637 return false;
638 }
639
640 MOZ_ASSERT(header->capacity >= header->initializedLength);
641 uint32_t unusedCapacity = header->capacity - header->initializedLength;
642
643 // Determine toShift, the number of extra elements we want to make
644 // available.
645 uint32_t toShift = count - numShifted;
646 MOZ_ASSERT(toShift <= ObjectElements::MaxShiftedElements,
647 "count <= MaxShiftedElements so toShift <= MaxShiftedElements");
648
649 // Give up if we need to allocate more elements.
650 if (toShift > unusedCapacity) {
651 return false;
652 }
653
654 // Move more elements than we need, so that other unshift calls will be
655 // fast. We just have to make sure we don't exceed unusedCapacity.
656 toShift = std::min(toShift + unusedCapacity / 2, unusedCapacity);
657
658 // Ensure |numShifted + toShift| does not exceed MaxShiftedElements.
659 if (numShifted + toShift > ObjectElements::MaxShiftedElements) {
660 toShift = ObjectElements::MaxShiftedElements - numShifted;
661 }
662
663 MOZ_ASSERT(count <= numShifted + toShift);
664 MOZ_ASSERT(numShifted + toShift <= ObjectElements::MaxShiftedElements);
665 MOZ_ASSERT(toShift <= unusedCapacity);
666
667 // Now move/unshift the elements.
668 uint32_t initLen = header->initializedLength;
669 setDenseInitializedLength(initLen + toShift);
670 for (uint32_t i = 0; i < toShift; i++) {
671 initDenseElement(initLen + i, UndefinedValue());
672 }
673 moveDenseElements(toShift, 0, initLen);
674
675 // Shift the elements we just prepended.
676 shiftDenseElementsUnchecked(toShift);
677
678 // We can now fall-through to the fast path below.
679 header = getElementsHeader();
680 MOZ_ASSERT(header->numShiftedElements() == numShifted + toShift);
681
682 numShifted = header->numShiftedElements();
683 MOZ_ASSERT(count <= numShifted);
684 }
685
686 elements_ -= count;
687 ObjectElements* newHeader = getElementsHeader();
688 memmove(newHeader, header, sizeof(ObjectElements));
689
690 newHeader->unshiftShiftedElements(count);
691
692 // Initialize to |undefined| to ensure pre-barriers don't see garbage.
693 for (uint32_t i = 0; i < count; i++) {
694 initDenseElement(i, UndefinedValue());
695 }
696
697 return true;
698 }
699
700 // Given a requested capacity (in elements) and (potentially) the length of an
701 // array for which elements are being allocated, compute an actual allocation
702 // amount (in elements). (Allocation amounts include space for an
703 // ObjectElements instance, so a return value of |N| implies
704 // |N - ObjectElements::VALUES_PER_HEADER| usable elements.)
705 //
706 // The requested/actual allocation distinction is meant to:
707 //
708 // * preserve amortized O(N) time to add N elements;
709 // * minimize the number of unused elements beyond an array's length, and
710 // * provide at least SLOT_CAPACITY_MIN elements no matter what (so adding
711 // the first several elements to small arrays only needs one allocation).
712 //
713 // Note: the structure and behavior of this method follow along with
714 // UnboxedArrayObject::chooseCapacityIndex. Changes to the allocation strategy
715 // in one should generally be matched by the other.
716 /* static */
goodElementsAllocationAmount(JSContext * cx,uint32_t reqCapacity,uint32_t length,uint32_t * goodAmount)717 bool NativeObject::goodElementsAllocationAmount(JSContext* cx,
718 uint32_t reqCapacity,
719 uint32_t length,
720 uint32_t* goodAmount) {
721 if (reqCapacity > MAX_DENSE_ELEMENTS_COUNT) {
722 ReportOutOfMemory(cx);
723 return false;
724 }
725
726 uint32_t reqAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER;
727
728 // Handle "small" requests primarily by doubling.
729 const uint32_t Mebi = 1 << 20;
730 if (reqAllocated < Mebi) {
731 uint32_t amount =
732 mozilla::AssertedCast<uint32_t>(RoundUpPow2(reqAllocated));
733
734 // If |amount| would be 2/3 or more of the array's length, adjust
735 // it (up or down) to be equal to the array's length. This avoids
736 // allocating excess elements that aren't likely to be needed, either
737 // in this resizing or a subsequent one. The 2/3 factor is chosen so
738 // that exceptional resizings will at most triple the capacity, as
739 // opposed to the usual doubling.
740 uint32_t goodCapacity = amount - ObjectElements::VALUES_PER_HEADER;
741 if (length >= reqCapacity && goodCapacity > (length / 3) * 2) {
742 amount = length + ObjectElements::VALUES_PER_HEADER;
743 }
744
745 if (amount < SLOT_CAPACITY_MIN) {
746 amount = SLOT_CAPACITY_MIN;
747 }
748
749 *goodAmount = amount;
750
751 return true;
752 }
753
754 // The almost-doubling above wastes a lot of space for larger bucket sizes.
755 // For large amounts, switch to bucket sizes that obey this formula:
756 //
757 // count(n+1) = Math.ceil(count(n) * 1.125)
758 //
759 // where |count(n)| is the size of the nth bucket, measured in 2**20 slots.
760 // These bucket sizes still preserve amortized O(N) time to add N elements,
761 // just with a larger constant factor.
762 //
763 // The bucket size table below was generated with this JavaScript (and
764 // manual reformatting):
765 //
766 // for (let n = 1, i = 0; i < 34; i++) {
767 // print('0x' + (n * (1 << 20)).toString(16) + ', ');
768 // n = Math.ceil(n * 1.125);
769 // }
770 static constexpr uint32_t BigBuckets[] = {
771 0x100000, 0x200000, 0x300000, 0x400000, 0x500000, 0x600000,
772 0x700000, 0x800000, 0x900000, 0xb00000, 0xd00000, 0xf00000,
773 0x1100000, 0x1400000, 0x1700000, 0x1a00000, 0x1e00000, 0x2200000,
774 0x2700000, 0x2c00000, 0x3200000, 0x3900000, 0x4100000, 0x4a00000,
775 0x5400000, 0x5f00000, 0x6b00000, 0x7900000, 0x8900000, 0x9b00000,
776 0xaf00000, 0xc500000, 0xde00000, 0xfa00000};
777 static_assert(BigBuckets[std::size(BigBuckets) - 1] <=
778 MAX_DENSE_ELEMENTS_ALLOCATION);
779
780 // Pick the first bucket that'll fit |reqAllocated|.
781 for (uint32_t b : BigBuckets) {
782 if (b >= reqAllocated) {
783 *goodAmount = b;
784 return true;
785 }
786 }
787
788 // Otherwise, return the maximum bucket size.
789 *goodAmount = MAX_DENSE_ELEMENTS_ALLOCATION;
790 return true;
791 }
792
growElements(JSContext * cx,uint32_t reqCapacity)793 bool NativeObject::growElements(JSContext* cx, uint32_t reqCapacity) {
794 MOZ_ASSERT(isExtensible());
795 MOZ_ASSERT(canHaveNonEmptyElements());
796
797 // If there are shifted elements, consider moving them first. If we don't
798 // move them here, the code below will include the shifted elements in the
799 // resize.
800 uint32_t numShifted = getElementsHeader()->numShiftedElements();
801 if (numShifted > 0) {
802 // If the number of elements is small, it's cheaper to just move them as
803 // it may avoid a malloc/realloc. Note that there's no technical reason
804 // for using this particular value, but it works well in real-world use
805 // cases.
806 static const size_t MaxElementsToMoveEagerly = 20;
807
808 if (getElementsHeader()->initializedLength <= MaxElementsToMoveEagerly) {
809 moveShiftedElements();
810 } else {
811 maybeMoveShiftedElements();
812 }
813 if (getDenseCapacity() >= reqCapacity) {
814 return true;
815 }
816 numShifted = getElementsHeader()->numShiftedElements();
817
818 // If |reqCapacity + numShifted| overflows, we just move all shifted
819 // elements to avoid the problem.
820 CheckedInt<uint32_t> checkedReqCapacity(reqCapacity);
821 checkedReqCapacity += numShifted;
822 if (MOZ_UNLIKELY(!checkedReqCapacity.isValid())) {
823 moveShiftedElements();
824 numShifted = 0;
825 }
826 }
827
828 uint32_t oldCapacity = getDenseCapacity();
829 MOZ_ASSERT(oldCapacity < reqCapacity);
830
831 uint32_t newAllocated = 0;
832 if (is<ArrayObject>() && !as<ArrayObject>().lengthIsWritable()) {
833 MOZ_ASSERT(reqCapacity <= as<ArrayObject>().length());
834 MOZ_ASSERT(reqCapacity <= MAX_DENSE_ELEMENTS_COUNT);
835 // Preserve the |capacity <= length| invariant for arrays with
836 // non-writable length. See also js::ArraySetLength which initially
837 // enforces this requirement.
838 newAllocated = reqCapacity + numShifted + ObjectElements::VALUES_PER_HEADER;
839 } else {
840 if (!goodElementsAllocationAmount(cx, reqCapacity + numShifted,
841 getElementsHeader()->length,
842 &newAllocated)) {
843 return false;
844 }
845 }
846
847 uint32_t newCapacity =
848 newAllocated - ObjectElements::VALUES_PER_HEADER - numShifted;
849 MOZ_ASSERT(newCapacity > oldCapacity && newCapacity >= reqCapacity);
850
851 // If newCapacity exceeds MAX_DENSE_ELEMENTS_COUNT, the array should become
852 // sparse.
853 MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
854
855 uint32_t initlen = getDenseInitializedLength();
856
857 HeapSlot* oldHeaderSlots =
858 reinterpret_cast<HeapSlot*>(getUnshiftedElementsHeader());
859 HeapSlot* newHeaderSlots;
860 uint32_t oldAllocated = 0;
861 if (hasDynamicElements()) {
862 MOZ_ASSERT(oldCapacity <= MAX_DENSE_ELEMENTS_COUNT);
863 oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER + numShifted;
864
865 newHeaderSlots = ReallocateObjectBuffer<HeapSlot>(
866 cx, this, oldHeaderSlots, oldAllocated, newAllocated);
867 if (!newHeaderSlots) {
868 return false; // Leave elements at its old size.
869 }
870 } else {
871 newHeaderSlots = AllocateObjectBuffer<HeapSlot>(cx, this, newAllocated);
872 if (!newHeaderSlots) {
873 return false; // Leave elements at its old size.
874 }
875 PodCopy(newHeaderSlots, oldHeaderSlots,
876 ObjectElements::VALUES_PER_HEADER + initlen + numShifted);
877 }
878
879 if (oldAllocated) {
880 RemoveCellMemory(this, oldAllocated * sizeof(HeapSlot),
881 MemoryUse::ObjectElements);
882 }
883
884 ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
885 elements_ = newheader->elements() + numShifted;
886 getElementsHeader()->capacity = newCapacity;
887
888 Debug_SetSlotRangeToCrashOnTouch(elements_ + initlen, newCapacity - initlen);
889
890 AddCellMemory(this, newAllocated * sizeof(HeapSlot),
891 MemoryUse::ObjectElements);
892
893 return true;
894 }
895
shrinkElements(JSContext * cx,uint32_t reqCapacity)896 void NativeObject::shrinkElements(JSContext* cx, uint32_t reqCapacity) {
897 MOZ_ASSERT(canHaveNonEmptyElements());
898 MOZ_ASSERT(reqCapacity >= getDenseInitializedLength());
899
900 if (!hasDynamicElements()) {
901 return;
902 }
903
904 // If we have shifted elements, consider moving them.
905 uint32_t numShifted = getElementsHeader()->numShiftedElements();
906 if (numShifted > 0) {
907 maybeMoveShiftedElements();
908 numShifted = getElementsHeader()->numShiftedElements();
909 }
910
911 uint32_t oldCapacity = getDenseCapacity();
912 MOZ_ASSERT(reqCapacity < oldCapacity);
913
914 uint32_t newAllocated = 0;
915 MOZ_ALWAYS_TRUE(goodElementsAllocationAmount(cx, reqCapacity + numShifted, 0,
916 &newAllocated));
917 MOZ_ASSERT(oldCapacity <= MAX_DENSE_ELEMENTS_COUNT);
918
919 uint32_t oldAllocated =
920 oldCapacity + ObjectElements::VALUES_PER_HEADER + numShifted;
921 if (newAllocated == oldAllocated) {
922 return; // Leave elements at its old size.
923 }
924
925 MOZ_ASSERT(newAllocated > ObjectElements::VALUES_PER_HEADER);
926 uint32_t newCapacity =
927 newAllocated - ObjectElements::VALUES_PER_HEADER - numShifted;
928 MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
929
930 HeapSlot* oldHeaderSlots =
931 reinterpret_cast<HeapSlot*>(getUnshiftedElementsHeader());
932 HeapSlot* newHeaderSlots = ReallocateObjectBuffer<HeapSlot>(
933 cx, this, oldHeaderSlots, oldAllocated, newAllocated);
934 if (!newHeaderSlots) {
935 cx->recoverFromOutOfMemory();
936 return; // Leave elements at its old size.
937 }
938
939 RemoveCellMemory(this, oldAllocated * sizeof(HeapSlot),
940 MemoryUse::ObjectElements);
941
942 ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
943 elements_ = newheader->elements() + numShifted;
944 getElementsHeader()->capacity = newCapacity;
945
946 AddCellMemory(this, newAllocated * sizeof(HeapSlot),
947 MemoryUse::ObjectElements);
948 }
949
shrinkCapacityToInitializedLength(JSContext * cx)950 void NativeObject::shrinkCapacityToInitializedLength(JSContext* cx) {
951 // When an array's length becomes non-writable, writes to indexes greater
952 // greater than or equal to the length don't change the array. We handle this
953 // with a check for non-writable length in most places. But in JIT code every
954 // check counts -- so we piggyback the check on the already-required range
955 // check for |index < capacity| by making capacity of arrays with non-writable
956 // length never exceed the length. This mechanism is also used when an object
957 // becomes non-extensible.
958
959 if (getElementsHeader()->numShiftedElements() > 0) {
960 moveShiftedElements();
961 }
962
963 ObjectElements* header = getElementsHeader();
964 uint32_t len = header->initializedLength;
965 MOZ_ASSERT(header->capacity >= len);
966 if (header->capacity == len) {
967 return;
968 }
969
970 shrinkElements(cx, len);
971
972 header = getElementsHeader();
973 uint32_t oldAllocated = header->numAllocatedElements();
974 header->capacity = len;
975
976 // The size of the memory allocation hasn't changed but we lose the actual
977 // capacity information. Make the associated size match the updated capacity.
978 if (!hasFixedElements()) {
979 uint32_t newAllocated = header->numAllocatedElements();
980 RemoveCellMemory(this, oldAllocated * sizeof(HeapSlot),
981 MemoryUse::ObjectElements);
982 AddCellMemory(this, newAllocated * sizeof(HeapSlot),
983 MemoryUse::ObjectElements);
984 }
985 }
986
987 /* static */
allocDictionarySlot(JSContext * cx,HandleNativeObject obj,uint32_t * slotp)988 bool NativeObject::allocDictionarySlot(JSContext* cx, HandleNativeObject obj,
989 uint32_t* slotp) {
990 MOZ_ASSERT(obj->inDictionaryMode());
991
992 uint32_t slotSpan = obj->slotSpan();
993 MOZ_ASSERT(slotSpan >= JSSLOT_FREE(obj->getClass()));
994
995 // Try to pull a free slot from the slot-number free list.
996 DictionaryPropMap* map = obj->shape()->dictionaryPropMap();
997 uint32_t last = map->freeList();
998 if (last != SHAPE_INVALID_SLOT) {
999 #ifdef DEBUG
1000 MOZ_ASSERT(last < slotSpan);
1001 uint32_t next = obj->getSlot(last).toPrivateUint32();
1002 MOZ_ASSERT_IF(next != SHAPE_INVALID_SLOT, next < slotSpan);
1003 #endif
1004 *slotp = last;
1005 const Value& vref = obj->getSlot(last);
1006 map->setFreeList(vref.toPrivateUint32());
1007 obj->setSlot(last, UndefinedValue());
1008 return true;
1009 }
1010
1011 if (MOZ_UNLIKELY(slotSpan >= SHAPE_MAXIMUM_SLOT)) {
1012 ReportOutOfMemory(cx);
1013 return false;
1014 }
1015
1016 *slotp = slotSpan;
1017
1018 return obj->ensureSlotsForDictionaryObject(cx, slotSpan + 1);
1019 }
1020
freeDictionarySlot(uint32_t slot)1021 void NativeObject::freeDictionarySlot(uint32_t slot) {
1022 MOZ_ASSERT(inDictionaryMode());
1023 MOZ_ASSERT(slot < slotSpan());
1024
1025 DictionaryPropMap* map = shape()->dictionaryPropMap();
1026 uint32_t last = map->freeList();
1027
1028 // Can't afford to check the whole free list, but let's check the head.
1029 MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan() && last != slot);
1030
1031 // Place all freed slots other than reserved slots (bug 595230) on the
1032 // dictionary's free list.
1033 if (JSSLOT_FREE(getClass()) <= slot) {
1034 MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan());
1035 setSlot(slot, PrivateUint32Value(last));
1036 map->setFreeList(slot);
1037 } else {
1038 setSlot(slot, UndefinedValue());
1039 }
1040 }
1041
1042 template <AllowGC allowGC>
NativeLookupOwnProperty(JSContext * cx,typename MaybeRooted<NativeObject *,allowGC>::HandleType obj,typename MaybeRooted<jsid,allowGC>::HandleType id,PropertyResult * propp)1043 bool js::NativeLookupOwnProperty(
1044 JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
1045 typename MaybeRooted<jsid, allowGC>::HandleType id, PropertyResult* propp) {
1046 return NativeLookupOwnPropertyInline<allowGC>(cx, obj, id, propp);
1047 }
1048
1049 template bool js::NativeLookupOwnProperty<CanGC>(JSContext* cx,
1050 HandleNativeObject obj,
1051 HandleId id,
1052 PropertyResult* propp);
1053
1054 template bool js::NativeLookupOwnProperty<NoGC>(JSContext* cx,
1055 NativeObject* const& obj,
1056 const jsid& id,
1057 PropertyResult* propp);
1058
1059 /*** [[DefineOwnProperty]] **************************************************/
1060
CallJSAddPropertyOp(JSContext * cx,JSAddPropertyOp op,HandleObject obj,HandleId id,HandleValue v)1061 static bool CallJSAddPropertyOp(JSContext* cx, JSAddPropertyOp op,
1062 HandleObject obj, HandleId id, HandleValue v) {
1063 AutoCheckRecursionLimit recursion(cx);
1064 if (!recursion.check(cx)) {
1065 return false;
1066 }
1067
1068 cx->check(obj, id, v);
1069 return op(cx, obj, id, v);
1070 }
1071
CallAddPropertyHook(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue value)1072 static MOZ_ALWAYS_INLINE bool CallAddPropertyHook(JSContext* cx,
1073 HandleNativeObject obj,
1074 HandleId id,
1075 HandleValue value) {
1076 JSAddPropertyOp addProperty = obj->getClass()->getAddProperty();
1077 if (MOZ_UNLIKELY(addProperty)) {
1078 MOZ_ASSERT(!cx->isHelperThreadContext());
1079
1080 if (!CallJSAddPropertyOp(cx, addProperty, obj, id, value)) {
1081 NativeObject::removeProperty(cx, obj, id);
1082 return false;
1083 }
1084 }
1085 return true;
1086 }
1087
CallAddPropertyHookDense(JSContext * cx,HandleNativeObject obj,uint32_t index,HandleValue value)1088 static MOZ_ALWAYS_INLINE bool CallAddPropertyHookDense(JSContext* cx,
1089 HandleNativeObject obj,
1090 uint32_t index,
1091 HandleValue value) {
1092 // Inline addProperty for array objects.
1093 if (obj->is<ArrayObject>()) {
1094 ArrayObject* arr = &obj->as<ArrayObject>();
1095 uint32_t length = arr->length();
1096 if (index >= length) {
1097 arr->setLength(index + 1);
1098 }
1099 return true;
1100 }
1101
1102 JSAddPropertyOp addProperty = obj->getClass()->getAddProperty();
1103 if (MOZ_UNLIKELY(addProperty)) {
1104 MOZ_ASSERT(!cx->isHelperThreadContext());
1105
1106 RootedId id(cx, INT_TO_JSID(index));
1107 if (!CallJSAddPropertyOp(cx, addProperty, obj, id, value)) {
1108 obj->setDenseElementHole(index);
1109 return false;
1110 }
1111 }
1112 return true;
1113 }
1114
1115 /**
1116 * Determines whether a write to the given element on |arr| should fail
1117 * because |arr| has a non-writable length, and writing that element would
1118 * increase the length of the array.
1119 */
WouldDefinePastNonwritableLength(ArrayObject * arr,uint32_t index)1120 static bool WouldDefinePastNonwritableLength(ArrayObject* arr, uint32_t index) {
1121 return !arr->lengthIsWritable() && index >= arr->length();
1122 }
1123
ReshapeForShadowedPropSlow(JSContext * cx,HandleNativeObject obj,HandleId id)1124 static bool ReshapeForShadowedPropSlow(JSContext* cx, HandleNativeObject obj,
1125 HandleId id) {
1126 MOZ_ASSERT(obj->isUsedAsPrototype());
1127
1128 // Lookups on integer ids cannot be cached through prototypes.
1129 if (JSID_IS_INT(id)) {
1130 return true;
1131 }
1132
1133 RootedObject proto(cx, obj->staticPrototype());
1134 while (proto) {
1135 // Lookups will not be cached through non-native protos.
1136 if (!proto->is<NativeObject>()) {
1137 break;
1138 }
1139
1140 if (proto->as<NativeObject>().contains(cx, id)) {
1141 return NativeObject::reshapeForShadowedProp(cx, proto.as<NativeObject>());
1142 }
1143
1144 proto = proto->staticPrototype();
1145 }
1146
1147 return true;
1148 }
1149
ReshapeForShadowedProp(JSContext * cx,HandleObject obj,HandleId id)1150 static MOZ_ALWAYS_INLINE bool ReshapeForShadowedProp(JSContext* cx,
1151 HandleObject obj,
1152 HandleId id) {
1153 // If |obj| is a prototype of another object, check if we're shadowing a
1154 // property on its proto chain. In this case we need to reshape that object
1155 // for shape teleporting to work correctly.
1156 //
1157 // See also the 'Shape Teleporting Optimization' comment in jit/CacheIR.cpp.
1158
1159 // Inlined fast path for non-prototype/non-native objects.
1160 if (!obj->isUsedAsPrototype() || !obj->is<NativeObject>()) {
1161 return true;
1162 }
1163
1164 return ReshapeForShadowedPropSlow(cx, obj.as<NativeObject>(), id);
1165 }
1166
1167 /* static */
reshapeForShadowedProp(JSContext * cx,HandleNativeObject obj)1168 bool NativeObject::reshapeForShadowedProp(JSContext* cx,
1169 HandleNativeObject obj) {
1170 if (!obj->inDictionaryMode()) {
1171 return toDictionaryMode(cx, obj);
1172 }
1173 return generateNewDictionaryShape(cx, obj);
1174 }
1175
ChangeProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleObject getter,HandleObject setter,PropertyFlags flags,PropertyResult * existing)1176 static bool ChangeProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
1177 HandleObject getter, HandleObject setter,
1178 PropertyFlags flags, PropertyResult* existing) {
1179 MOZ_ASSERT(existing);
1180
1181 Rooted<GetterSetter*> gs(cx);
1182
1183 // If we're redefining a getter/setter property but the getter and setter
1184 // objects are still the same, use the existing GetterSetter.
1185 if (existing->isNativeProperty()) {
1186 PropertyInfo prop = existing->propertyInfo();
1187 if (prop.isAccessorProperty()) {
1188 GetterSetter* current = obj->getGetterSetter(prop);
1189 if (current->getter() == getter && current->setter() == setter) {
1190 gs = current;
1191 }
1192 }
1193 }
1194
1195 if (!gs) {
1196 gs = GetterSetter::create(cx, getter, setter);
1197 if (!gs) {
1198 return false;
1199 }
1200 }
1201
1202 uint32_t slot;
1203 if (existing->isNativeProperty()) {
1204 if (!NativeObject::changeProperty(cx, obj, id, flags, &slot)) {
1205 return false;
1206 }
1207 } else {
1208 if (!NativeObject::addProperty(cx, obj, id, flags, &slot)) {
1209 return false;
1210 }
1211 }
1212
1213 obj->setSlot(slot, PrivateGCThingValue(gs));
1214 return true;
1215 }
1216
ComputePropertyFlags(const PropertyDescriptor & desc)1217 static PropertyFlags ComputePropertyFlags(const PropertyDescriptor& desc) {
1218 desc.assertComplete();
1219
1220 PropertyFlags flags;
1221 flags.setFlag(PropertyFlag::Configurable, desc.configurable());
1222 flags.setFlag(PropertyFlag::Enumerable, desc.enumerable());
1223
1224 if (desc.isDataDescriptor()) {
1225 flags.setFlag(PropertyFlag::Writable, desc.writable());
1226 } else {
1227 MOZ_ASSERT(desc.isAccessorDescriptor());
1228 flags.setFlag(PropertyFlag::AccessorProperty);
1229 }
1230
1231 return flags;
1232 }
1233
1234 // Whether we're adding a new property or changing an existing property (this
1235 // can be either a property stored in the shape tree or a dense element).
1236 enum class IsAddOrChange { Add, Change };
1237
1238 template <IsAddOrChange AddOrChange>
AddOrChangeProperty(JSContext * cx,HandleNativeObject obj,HandleId id,Handle<PropertyDescriptor> desc,PropertyResult * existing=nullptr)1239 static MOZ_ALWAYS_INLINE bool AddOrChangeProperty(
1240 JSContext* cx, HandleNativeObject obj, HandleId id,
1241 Handle<PropertyDescriptor> desc, PropertyResult* existing = nullptr) {
1242 desc.assertComplete();
1243
1244 #ifdef DEBUG
1245 if constexpr (AddOrChange == IsAddOrChange::Add) {
1246 MOZ_ASSERT(existing == nullptr);
1247 MOZ_ASSERT(!obj->containsPure(id));
1248 } else {
1249 static_assert(AddOrChange == IsAddOrChange::Change);
1250 MOZ_ASSERT(existing);
1251 MOZ_ASSERT(existing->isNativeProperty() || existing->isDenseElement());
1252 }
1253 #endif
1254
1255 if (!ReshapeForShadowedProp(cx, obj, id)) {
1256 return false;
1257 }
1258
1259 // Use dense storage for indexed properties where possible: when we have an
1260 // integer key with default property attributes and are either adding a new
1261 // property or changing a dense element.
1262 PropertyFlags flags = ComputePropertyFlags(desc);
1263 if (id.isInt() && flags == PropertyFlags::defaultDataPropFlags &&
1264 (AddOrChange == IsAddOrChange::Add || existing->isDenseElement())) {
1265 MOZ_ASSERT(!desc.isAccessorDescriptor());
1266 MOZ_ASSERT(!obj->is<TypedArrayObject>());
1267 uint32_t index = JSID_TO_INT(id);
1268 DenseElementResult edResult = obj->ensureDenseElements(cx, index, 1);
1269 if (edResult == DenseElementResult::Failure) {
1270 return false;
1271 }
1272 if (edResult == DenseElementResult::Success) {
1273 obj->setDenseElement(index, desc.value());
1274 if (!CallAddPropertyHookDense(cx, obj, index, desc.value())) {
1275 return false;
1276 }
1277 return true;
1278 }
1279 }
1280
1281 if constexpr (AddOrChange == IsAddOrChange::Add) {
1282 if (desc.isAccessorDescriptor()) {
1283 Rooted<GetterSetter*> gs(
1284 cx, GetterSetter::create(cx, desc.getter(), desc.setter()));
1285 if (!gs) {
1286 return false;
1287 }
1288 uint32_t slot;
1289 if (!NativeObject::addProperty(cx, obj, id, flags, &slot)) {
1290 return false;
1291 }
1292 obj->initSlot(slot, PrivateGCThingValue(gs));
1293 } else {
1294 uint32_t slot;
1295 if (!NativeObject::addProperty(cx, obj, id, flags, &slot)) {
1296 return false;
1297 }
1298 obj->initSlot(slot, desc.value());
1299 }
1300 } else {
1301 if (desc.isAccessorDescriptor()) {
1302 if (!ChangeProperty(cx, obj, id, desc.getter(), desc.setter(), flags,
1303 existing)) {
1304 return false;
1305 }
1306 } else {
1307 uint32_t slot;
1308 if (existing->isNativeProperty()) {
1309 if (!NativeObject::changeProperty(cx, obj, id, flags, &slot)) {
1310 return false;
1311 }
1312 } else {
1313 if (!NativeObject::addProperty(cx, obj, id, flags, &slot)) {
1314 return false;
1315 }
1316 }
1317 obj->setSlot(slot, desc.value());
1318 }
1319 }
1320
1321 // Clear any existing dense index after adding a sparse indexed property,
1322 // and investigate converting the object to dense indexes.
1323 if (JSID_IS_INT(id)) {
1324 uint32_t index = JSID_TO_INT(id);
1325 if constexpr (AddOrChange == IsAddOrChange::Add) {
1326 MOZ_ASSERT(!obj->containsDenseElement(index));
1327 } else {
1328 obj->removeDenseElementForSparseIndex(index);
1329 }
1330 DenseElementResult edResult =
1331 NativeObject::maybeDensifySparseElements(cx, obj);
1332 if (edResult == DenseElementResult::Failure) {
1333 return false;
1334 }
1335 if (edResult == DenseElementResult::Success) {
1336 MOZ_ASSERT(!desc.isAccessorDescriptor());
1337 return CallAddPropertyHookDense(cx, obj, index, desc.value());
1338 }
1339 }
1340
1341 if (desc.isDataDescriptor()) {
1342 return CallAddPropertyHook(cx, obj, id, desc.value());
1343 }
1344
1345 return CallAddPropertyHook(cx, obj, id, UndefinedHandleValue);
1346 }
1347
1348 // Versions of AddOrChangeProperty optimized for adding a plain data property.
1349 // These function doesn't handle integer ids as we may have to store them in
1350 // dense elements.
AddDataProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue v)1351 static MOZ_ALWAYS_INLINE bool AddDataProperty(JSContext* cx,
1352 HandleNativeObject obj,
1353 HandleId id, HandleValue v) {
1354 MOZ_ASSERT(!JSID_IS_INT(id));
1355
1356 if (!ReshapeForShadowedProp(cx, obj, id)) {
1357 return false;
1358 }
1359
1360 uint32_t slot;
1361 if (!NativeObject::addProperty(cx, obj, id,
1362 PropertyFlags::defaultDataPropFlags, &slot)) {
1363 return false;
1364 }
1365
1366 obj->initSlot(slot, v);
1367
1368 return CallAddPropertyHook(cx, obj, id, v);
1369 }
1370
IsAccessorDescriptor(const PropertyResult & prop)1371 static bool IsAccessorDescriptor(const PropertyResult& prop) {
1372 if (prop.isNativeProperty()) {
1373 return prop.propertyInfo().isAccessorProperty();
1374 }
1375
1376 MOZ_ASSERT(prop.isDenseElement() || prop.isTypedArrayElement());
1377 return false;
1378 }
1379
IsDataDescriptor(const PropertyResult & prop)1380 static bool IsDataDescriptor(const PropertyResult& prop) {
1381 return !IsAccessorDescriptor(prop);
1382 }
1383
1384 static bool GetCustomDataProperty(JSContext* cx, HandleObject obj, HandleId id,
1385 MutableHandleValue vp);
1386
GetExistingDataProperty(JSContext * cx,HandleNativeObject obj,HandleId id,const PropertyResult & prop,MutableHandleValue vp)1387 static bool GetExistingDataProperty(JSContext* cx, HandleNativeObject obj,
1388 HandleId id, const PropertyResult& prop,
1389 MutableHandleValue vp) {
1390 if (prop.isDenseElement()) {
1391 vp.set(obj->getDenseElement(prop.denseElementIndex()));
1392 return true;
1393 }
1394 if (prop.isTypedArrayElement()) {
1395 size_t idx = prop.typedArrayElementIndex();
1396 return obj->as<TypedArrayObject>().getElement<CanGC>(cx, idx, vp);
1397 }
1398
1399 PropertyInfo propInfo = prop.propertyInfo();
1400 if (propInfo.isDataProperty()) {
1401 vp.set(obj->getSlot(propInfo.slot()));
1402 return true;
1403 }
1404
1405 MOZ_ASSERT(!cx->isHelperThreadContext());
1406 MOZ_RELEASE_ASSERT(propInfo.isCustomDataProperty());
1407 return GetCustomDataProperty(cx, obj, id, vp);
1408 }
1409
1410 /*
1411 * If desc is redundant with an existing own property obj[id], then set
1412 * |*redundant = true| and return true.
1413 */
DefinePropertyIsRedundant(JSContext * cx,HandleNativeObject obj,HandleId id,const PropertyResult & prop,JS::PropertyAttributes attrs,Handle<PropertyDescriptor> desc,bool * redundant)1414 static bool DefinePropertyIsRedundant(JSContext* cx, HandleNativeObject obj,
1415 HandleId id, const PropertyResult& prop,
1416 JS::PropertyAttributes attrs,
1417 Handle<PropertyDescriptor> desc,
1418 bool* redundant) {
1419 *redundant = false;
1420
1421 if (desc.hasConfigurable() && desc.configurable() != attrs.configurable()) {
1422 return true;
1423 }
1424 if (desc.hasEnumerable() && desc.enumerable() != attrs.enumerable()) {
1425 return true;
1426 }
1427 if (desc.isDataDescriptor()) {
1428 if (IsAccessorDescriptor(prop)) {
1429 return true;
1430 }
1431 if (desc.hasWritable() && desc.writable() != attrs.writable()) {
1432 return true;
1433 }
1434 if (desc.hasValue()) {
1435 // Get the current value of the existing property.
1436 RootedValue currentValue(cx);
1437 if (!GetExistingDataProperty(cx, obj, id, prop, ¤tValue)) {
1438 return false;
1439 }
1440
1441 // Don't call SameValue here to ensure we properly update distinct
1442 // NaN values.
1443 if (desc.value() != currentValue) {
1444 return true;
1445 }
1446 }
1447
1448 // Check for custom data properties for ArrayObject/ArgumentsObject.
1449 // PropertyDescriptor can't represent these properties so they're never
1450 // redundant.
1451 if (prop.isNativeProperty() && prop.propertyInfo().isCustomDataProperty()) {
1452 return true;
1453 }
1454 } else if (desc.isAccessorDescriptor()) {
1455 if (!prop.isNativeProperty()) {
1456 return true;
1457 }
1458 PropertyInfo propInfo = prop.propertyInfo();
1459 if (desc.hasGetter() && (!propInfo.isAccessorProperty() ||
1460 desc.getter() != obj->getGetter(propInfo))) {
1461 return true;
1462 }
1463 if (desc.hasSetter() && (!propInfo.isAccessorProperty() ||
1464 desc.setter() != obj->getSetter(propInfo))) {
1465 return true;
1466 }
1467 }
1468
1469 *redundant = true;
1470 return true;
1471 }
1472
NativeDefineProperty(JSContext * cx,HandleNativeObject obj,HandleId id,Handle<PropertyDescriptor> desc_,ObjectOpResult & result)1473 bool js::NativeDefineProperty(JSContext* cx, HandleNativeObject obj,
1474 HandleId id, Handle<PropertyDescriptor> desc_,
1475 ObjectOpResult& result) {
1476 desc_.assertValid();
1477
1478 // Section numbers and step numbers below refer to ES2018, draft rev
1479 // 540b827fccf6122a984be99ab9af7be20e3b5562.
1480 //
1481 // This function aims to implement 9.1.6 [[DefineOwnProperty]] as well as
1482 // the [[DefineOwnProperty]] methods described in 9.4.2.1 (arrays), 9.4.4.2
1483 // (arguments), and 9.4.5.3 (typed array views).
1484
1485 // Dispense with custom behavior of exotic native objects first.
1486 if (obj->is<ArrayObject>()) {
1487 // 9.4.2.1 step 2. Redefining an array's length is very special.
1488 Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
1489 if (id == NameToId(cx->names().length)) {
1490 // 9.1.6.3 ValidateAndApplyPropertyDescriptor, step 7.a.
1491 if (desc_.isAccessorDescriptor()) {
1492 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1493 }
1494
1495 MOZ_ASSERT(!cx->isHelperThreadContext());
1496 return ArraySetLength(cx, arr, id, desc_, result);
1497 }
1498
1499 // 9.4.2.1 step 3. Don't extend a fixed-length array.
1500 uint32_t index;
1501 if (IdIsIndex(id, &index)) {
1502 if (WouldDefinePastNonwritableLength(arr, index)) {
1503 return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
1504 }
1505 }
1506 } else if (obj->is<TypedArrayObject>()) {
1507 // 9.4.5.3 step 3. Indexed properties of typed arrays are special.
1508 Rooted<TypedArrayObject*> tobj(cx, &obj->as<TypedArrayObject>());
1509 mozilla::Maybe<uint64_t> index;
1510 if (!ToTypedArrayIndex(cx, id, &index)) {
1511 return false;
1512 }
1513
1514 if (index) {
1515 MOZ_ASSERT(!cx->isHelperThreadContext());
1516 return DefineTypedArrayElement(cx, tobj, index.value(), desc_, result);
1517 }
1518 } else if (obj->is<ArgumentsObject>()) {
1519 Rooted<ArgumentsObject*> argsobj(cx, &obj->as<ArgumentsObject>());
1520 if (id.isAtom(cx->names().length)) {
1521 // Either we are resolving the .length property on this object,
1522 // or redefining it. In the latter case only, we must reify the
1523 // property.
1524 if (!desc_.resolving()) {
1525 if (!ArgumentsObject::reifyLength(cx, argsobj)) {
1526 return false;
1527 }
1528 }
1529 } else if (id.isWellKnownSymbol(JS::SymbolCode::iterator)) {
1530 // Do same thing as .length for [@@iterator].
1531 if (!desc_.resolving()) {
1532 if (!ArgumentsObject::reifyIterator(cx, argsobj)) {
1533 return false;
1534 }
1535 }
1536 } else if (id.isInt()) {
1537 if (!desc_.resolving()) {
1538 argsobj->markElementOverridden();
1539 }
1540 }
1541 }
1542
1543 // 9.1.6.1 OrdinaryDefineOwnProperty step 1.
1544 PropertyResult prop;
1545 if (desc_.resolving()) {
1546 // We are being called from a resolve or enumerate hook to reify a
1547 // lazily-resolved property. To avoid reentering the resolve hook and
1548 // recursing forever, skip the resolve hook when doing this lookup.
1549 if (!NativeLookupOwnPropertyNoResolve(cx, obj, id, &prop)) {
1550 return false;
1551 }
1552 } else {
1553 if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &prop)) {
1554 return false;
1555 }
1556 }
1557
1558 // From this point, the step numbers refer to
1559 // 9.1.6.3, ValidateAndApplyPropertyDescriptor.
1560 // Step 1 is a redundant assertion.
1561
1562 // Filling in desc: Here we make a copy of the desc_ argument. We will turn
1563 // it into a complete descriptor before updating obj. The spec algorithm
1564 // does not explicitly do this, but the end result is the same. Search for
1565 // "fill in" below for places where the filling-in actually occurs.
1566 Rooted<PropertyDescriptor> desc(cx, desc_);
1567
1568 // Step 2.
1569 if (prop.isNotFound()) {
1570 // Note: We are sharing the property definition machinery with private
1571 // fields. Private fields may be added to non-extensible objects.
1572 if (!obj->isExtensible() && !id.isPrivateName()) {
1573 return result.fail(JSMSG_CANT_DEFINE_PROP_OBJECT_NOT_EXTENSIBLE);
1574 }
1575
1576 // Fill in missing desc fields with defaults.
1577 CompletePropertyDescriptor(&desc);
1578
1579 if (!AddOrChangeProperty<IsAddOrChange::Add>(cx, obj, id, desc)) {
1580 return false;
1581 }
1582 return result.succeed();
1583 }
1584
1585 // Step 3 and 7.a.i.3, 8.a.iii, 10 (partially). Prop might not actually
1586 // have a real shape, e.g. in the case of typed array elements,
1587 // GetPropertyAttributes is used to paper-over that difference.
1588 JS::PropertyAttributes attrs = GetPropertyAttributes(obj, prop);
1589 bool redundant;
1590 if (!DefinePropertyIsRedundant(cx, obj, id, prop, attrs, desc, &redundant)) {
1591 return false;
1592 }
1593 if (redundant) {
1594 return result.succeed();
1595 }
1596
1597 // Step 4.
1598 if (!attrs.configurable()) {
1599 if (desc.hasConfigurable() && desc.configurable()) {
1600 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1601 }
1602 if (desc.hasEnumerable() && desc.enumerable() != attrs.enumerable()) {
1603 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1604 }
1605 }
1606
1607 // Fill in desc.[[Configurable]] and desc.[[Enumerable]] if missing.
1608 if (!desc.hasConfigurable()) {
1609 desc.setConfigurable(attrs.configurable());
1610 }
1611 if (!desc.hasEnumerable()) {
1612 desc.setEnumerable(attrs.enumerable());
1613 }
1614
1615 // Steps 5-8.
1616 if (desc.isGenericDescriptor()) {
1617 // Step 5. No further validation is required.
1618
1619 // Fill in desc. A generic descriptor has none of these fields, so copy
1620 // everything from shape.
1621 MOZ_ASSERT(!desc.hasValue());
1622 MOZ_ASSERT(!desc.hasWritable());
1623 MOZ_ASSERT(!desc.hasGetter());
1624 MOZ_ASSERT(!desc.hasSetter());
1625 if (IsDataDescriptor(prop)) {
1626 RootedValue currentValue(cx);
1627 if (!GetExistingDataProperty(cx, obj, id, prop, ¤tValue)) {
1628 return false;
1629 }
1630 desc.setValue(currentValue);
1631 desc.setWritable(attrs.writable());
1632 } else {
1633 PropertyInfo propInfo = prop.propertyInfo();
1634 desc.setGetter(obj->getGetter(propInfo));
1635 desc.setSetter(obj->getSetter(propInfo));
1636 }
1637 } else if (desc.isDataDescriptor() != IsDataDescriptor(prop)) {
1638 // Step 6.
1639 if (!attrs.configurable()) {
1640 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1641 }
1642
1643 // Fill in desc fields with default values (steps 6.b.i and 6.c.i).
1644 CompletePropertyDescriptor(&desc);
1645 } else if (desc.isDataDescriptor()) {
1646 // Step 7.
1647 bool frozen = !attrs.configurable() && !attrs.writable();
1648
1649 // Step 7.a.i.1.
1650 if (frozen && desc.hasWritable() && desc.writable()) {
1651 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1652 }
1653
1654 if (frozen || !desc.hasValue()) {
1655 RootedValue currentValue(cx);
1656 if (!GetExistingDataProperty(cx, obj, id, prop, ¤tValue)) {
1657 return false;
1658 }
1659
1660 if (!desc.hasValue()) {
1661 // Fill in desc.[[Value]].
1662 desc.setValue(currentValue);
1663 } else {
1664 // Step 7.a.i.2.
1665 bool same;
1666 MOZ_ASSERT(!cx->isHelperThreadContext());
1667 if (!SameValue(cx, desc.value(), currentValue, &same)) {
1668 return false;
1669 }
1670 if (!same) {
1671 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1672 }
1673 }
1674 }
1675
1676 // Step 7.a.i.3.
1677 if (frozen) {
1678 return result.succeed();
1679 }
1680
1681 // Fill in desc.[[Writable]].
1682 if (!desc.hasWritable()) {
1683 desc.setWritable(attrs.writable());
1684 }
1685 } else {
1686 // Step 8.
1687 PropertyInfo propInfo = prop.propertyInfo();
1688 MOZ_ASSERT(propInfo.isAccessorProperty());
1689 MOZ_ASSERT(desc.isAccessorDescriptor());
1690
1691 // The spec says to use SameValue, but since the values in
1692 // question are objects, we can just compare pointers.
1693 if (desc.hasSetter()) {
1694 // Step 8.a.i.
1695 if (!attrs.configurable() && desc.setter() != obj->getSetter(propInfo)) {
1696 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1697 }
1698 } else {
1699 // Fill in desc.[[Set]] from shape.
1700 desc.setSetter(obj->getSetter(propInfo));
1701 }
1702 if (desc.hasGetter()) {
1703 // Step 8.a.ii.
1704 if (!attrs.configurable() && desc.getter() != obj->getGetter(propInfo)) {
1705 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1706 }
1707 } else {
1708 // Fill in desc.[[Get]] from shape.
1709 desc.setGetter(obj->getGetter(propInfo));
1710 }
1711
1712 // Step 8.a.iii (Omitted).
1713 }
1714
1715 // Step 9.
1716 if (!AddOrChangeProperty<IsAddOrChange::Change>(cx, obj, id, desc, &prop)) {
1717 return false;
1718 }
1719
1720 // Step 10.
1721 return result.succeed();
1722 }
1723
NativeDefineDataProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue value,unsigned attrs,ObjectOpResult & result)1724 bool js::NativeDefineDataProperty(JSContext* cx, HandleNativeObject obj,
1725 HandleId id, HandleValue value,
1726 unsigned attrs, ObjectOpResult& result) {
1727 Rooted<PropertyDescriptor> desc(cx, PropertyDescriptor::Data(value, attrs));
1728 return NativeDefineProperty(cx, obj, id, desc, result);
1729 }
1730
NativeDefineAccessorProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleObject getter,HandleObject setter,unsigned attrs)1731 bool js::NativeDefineAccessorProperty(JSContext* cx, HandleNativeObject obj,
1732 HandleId id, HandleObject getter,
1733 HandleObject setter, unsigned attrs) {
1734 Rooted<PropertyDescriptor> desc(
1735 cx, PropertyDescriptor::Accessor(
1736 getter ? mozilla::Some(getter) : mozilla::Nothing(),
1737 setter ? mozilla::Some(setter) : mozilla::Nothing(), attrs));
1738
1739 ObjectOpResult result;
1740 if (!NativeDefineProperty(cx, obj, id, desc, result)) {
1741 return false;
1742 }
1743
1744 if (!result) {
1745 // Off-thread callers should not get here: they must call this
1746 // function only with known-valid arguments. Populating a new
1747 // PlainObject with configurable properties is fine.
1748 MOZ_ASSERT(!cx->isHelperThreadContext());
1749 result.reportError(cx, obj, id);
1750 return false;
1751 }
1752
1753 return true;
1754 }
1755
NativeDefineDataProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue value,unsigned attrs)1756 bool js::NativeDefineDataProperty(JSContext* cx, HandleNativeObject obj,
1757 HandleId id, HandleValue value,
1758 unsigned attrs) {
1759 ObjectOpResult result;
1760 if (!NativeDefineDataProperty(cx, obj, id, value, attrs, result)) {
1761 return false;
1762 }
1763 if (!result) {
1764 // Off-thread callers should not get here: they must call this
1765 // function only with known-valid arguments. Populating a new
1766 // PlainObject with configurable properties is fine.
1767 MOZ_ASSERT(!cx->isHelperThreadContext());
1768 result.reportError(cx, obj, id);
1769 return false;
1770 }
1771 return true;
1772 }
1773
NativeDefineDataProperty(JSContext * cx,HandleNativeObject obj,PropertyName * name,HandleValue value,unsigned attrs)1774 bool js::NativeDefineDataProperty(JSContext* cx, HandleNativeObject obj,
1775 PropertyName* name, HandleValue value,
1776 unsigned attrs) {
1777 RootedId id(cx, NameToId(name));
1778 return NativeDefineDataProperty(cx, obj, id, value, attrs);
1779 }
1780
DefineNonexistentProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue v,ObjectOpResult & result)1781 static bool DefineNonexistentProperty(JSContext* cx, HandleNativeObject obj,
1782 HandleId id, HandleValue v,
1783 ObjectOpResult& result) {
1784 // Optimized NativeDefineProperty() version for known absent properties.
1785
1786 // Dispense with custom behavior of exotic native objects first.
1787 if (obj->is<ArrayObject>()) {
1788 // Array's length property is non-configurable, so we shouldn't
1789 // encounter it in this function.
1790 MOZ_ASSERT(id != NameToId(cx->names().length));
1791
1792 // 9.4.2.1 step 3. Don't extend a fixed-length array.
1793 uint32_t index;
1794 if (IdIsIndex(id, &index)) {
1795 if (WouldDefinePastNonwritableLength(&obj->as<ArrayObject>(), index)) {
1796 return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
1797 }
1798 }
1799 } else if (obj->is<TypedArrayObject>()) {
1800 // 9.4.5.5 step 2. Indexed properties of typed arrays are special.
1801 mozilla::Maybe<uint64_t> index;
1802 if (!ToTypedArrayIndex(cx, id, &index)) {
1803 return false;
1804 }
1805
1806 if (index) {
1807 // This method is only called for non-existent properties, which
1808 // means any absent indexed property must be out of range.
1809 MOZ_ASSERT(index.value() >= obj->as<TypedArrayObject>().length());
1810
1811 // The following steps refer to 9.4.5.11 IntegerIndexedElementSet.
1812
1813 // Step 1 is enforced by the caller.
1814
1815 // Steps 2-3.
1816 // We still need to call ToNumber or ToBigInt, because of its
1817 // possible side effects.
1818 if (!obj->as<TypedArrayObject>().convertForSideEffect(cx, v)) {
1819 return false;
1820 }
1821
1822 // Step 4 (nothing to do, the index is out of range).
1823
1824 // Step 5.
1825 return result.succeed();
1826 }
1827 } else if (obj->is<ArgumentsObject>()) {
1828 // If this method is called with either |length| or |@@iterator|, the
1829 // property was previously deleted and hence should already be marked
1830 // as overridden.
1831 MOZ_ASSERT_IF(id.isAtom(cx->names().length),
1832 obj->as<ArgumentsObject>().hasOverriddenLength());
1833 MOZ_ASSERT_IF(id.isWellKnownSymbol(JS::SymbolCode::iterator),
1834 obj->as<ArgumentsObject>().hasOverriddenIterator());
1835
1836 // We still need to mark any element properties as overridden.
1837 if (JSID_IS_INT(id)) {
1838 obj->as<ArgumentsObject>().markElementOverridden();
1839 }
1840 }
1841
1842 #ifdef DEBUG
1843 PropertyResult prop;
1844 if (!NativeLookupOwnPropertyNoResolve(cx, obj, id, &prop)) {
1845 return false;
1846 }
1847 MOZ_ASSERT(prop.isNotFound(), "didn't expect to find an existing property");
1848 #endif
1849
1850 // 9.1.6.3, ValidateAndApplyPropertyDescriptor.
1851 // Step 1 is a redundant assertion, step 3 and later don't apply here.
1852
1853 // Step 2.
1854 if (!obj->isExtensible()) {
1855 return result.fail(JSMSG_CANT_DEFINE_PROP_OBJECT_NOT_EXTENSIBLE);
1856 }
1857
1858 if (id.isInt()) {
1859 // This might be a dense element. Use AddOrChangeProperty as it knows
1860 // how to deal with that.
1861 Rooted<PropertyDescriptor> desc(
1862 cx, PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
1863 JS::PropertyAttribute::Enumerable,
1864 JS::PropertyAttribute::Writable}));
1865 if (!AddOrChangeProperty<IsAddOrChange::Add>(cx, obj, id, desc)) {
1866 return false;
1867 }
1868 } else {
1869 if (!AddDataProperty(cx, obj, id, v)) {
1870 return false;
1871 }
1872 }
1873
1874 return result.succeed();
1875 }
1876
AddOrUpdateSparseElementHelper(JSContext * cx,HandleArrayObject obj,int32_t int_id,HandleValue v,bool strict)1877 bool js::AddOrUpdateSparseElementHelper(JSContext* cx, HandleArrayObject obj,
1878 int32_t int_id, HandleValue v,
1879 bool strict) {
1880 MOZ_ASSERT(INT_FITS_IN_JSID(int_id));
1881 RootedId id(cx, INT_TO_JSID(int_id));
1882
1883 // This helper doesn't handle the case where the index may be in the dense
1884 // elements
1885 MOZ_ASSERT(int_id >= 0);
1886 MOZ_ASSERT(uint32_t(int_id) >= obj->getDenseInitializedLength());
1887
1888 // First decide if this is an add or an update. Because the IC guards have
1889 // already ensured this exists exterior to the dense array range, and the
1890 // prototype checks have ensured there are no indexes on the prototype, we
1891 // can use the shape lineage to find the element if it exists:
1892 uint32_t index;
1893 PropMap* map = obj->shape()->lookup(cx, id, &index);
1894
1895 // If we didn't find the property, we're on the add path: delegate to
1896 // AddOrChangeProperty.
1897 if (map == nullptr) {
1898 Rooted<PropertyDescriptor> desc(
1899 cx, PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
1900 JS::PropertyAttribute::Enumerable,
1901 JS::PropertyAttribute::Writable}));
1902 return AddOrChangeProperty<IsAddOrChange::Add>(cx, obj, id, desc);
1903 }
1904
1905 // At this point we're updating a property: See SetExistingProperty.
1906 PropertyInfo prop = map->getPropertyInfo(index);
1907 if (prop.isDataProperty() && prop.writable()) {
1908 obj->setSlot(prop.slot(), v);
1909 return true;
1910 }
1911
1912 // We don't know exactly what this object looks like, hit the slowpath.
1913 RootedValue receiver(cx, ObjectValue(*obj));
1914 JS::ObjectOpResult result;
1915 return SetProperty(cx, obj, id, v, receiver, result) &&
1916 result.checkStrictModeError(cx, obj, id, strict);
1917 }
1918
1919 /*** [[HasProperty]] ********************************************************/
1920
1921 // ES6 draft rev31 9.1.7.1 OrdinaryHasProperty
NativeHasProperty(JSContext * cx,HandleNativeObject obj,HandleId id,bool * foundp)1922 bool js::NativeHasProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
1923 bool* foundp) {
1924 RootedNativeObject pobj(cx, obj);
1925 PropertyResult prop;
1926
1927 // This loop isn't explicit in the spec algorithm. See the comment on step
1928 // 7.a. below.
1929 for (;;) {
1930 // Steps 2-3.
1931 if (!NativeLookupOwnPropertyInline<CanGC>(cx, pobj, id, &prop)) {
1932 return false;
1933 }
1934
1935 // Step 4.
1936 if (prop.isFound()) {
1937 *foundp = true;
1938 return true;
1939 }
1940
1941 // Step 5-6.
1942 JSObject* proto = pobj->staticPrototype();
1943
1944 // Step 8.
1945 // As a side-effect of NativeLookupOwnPropertyInline, we may determine that
1946 // a property is not found and the proto chain should not be searched. This
1947 // can occur for:
1948 // - Out-of-range numeric properties of a TypedArrayObject
1949 // - Recursive resolve hooks (which is expected when they try to set the
1950 // property being resolved).
1951 if (!proto || prop.shouldIgnoreProtoChain()) {
1952 *foundp = false;
1953 return true;
1954 }
1955
1956 // Step 7.a. If the prototype is also native, this step is a
1957 // recursive tail call, and we don't need to go through all the
1958 // plumbing of HasProperty; the top of the loop is where
1959 // we're going to end up anyway. But if pobj is non-native,
1960 // that optimization would be incorrect.
1961 if (!proto->is<NativeObject>()) {
1962 RootedObject protoRoot(cx, proto);
1963 return HasProperty(cx, protoRoot, id, foundp);
1964 }
1965
1966 pobj = &proto->as<NativeObject>();
1967 }
1968 }
1969
1970 /*** [[GetOwnPropertyDescriptor]] *******************************************/
1971
NativeGetOwnPropertyDescriptor(JSContext * cx,HandleNativeObject obj,HandleId id,MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc)1972 bool js::NativeGetOwnPropertyDescriptor(
1973 JSContext* cx, HandleNativeObject obj, HandleId id,
1974 MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) {
1975 PropertyResult prop;
1976 if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &prop)) {
1977 return false;
1978 }
1979 if (prop.isNotFound()) {
1980 desc.reset();
1981 return true;
1982 }
1983
1984 if (prop.isNativeProperty() && prop.propertyInfo().isAccessorProperty()) {
1985 PropertyInfo propInfo = prop.propertyInfo();
1986 desc.set(mozilla::Some(PropertyDescriptor::Accessor(
1987 obj->getGetter(propInfo), obj->getSetter(propInfo),
1988 propInfo.propAttributes())));
1989 return true;
1990 }
1991
1992 RootedValue value(cx);
1993 if (!GetExistingDataProperty(cx, obj, id, prop, &value)) {
1994 return false;
1995 }
1996
1997 JS::PropertyAttributes attrs = GetPropertyAttributes(obj, prop);
1998 desc.set(mozilla::Some(PropertyDescriptor::Data(value, attrs)));
1999 return true;
2000 }
2001
2002 /*** [[Get]] ****************************************************************/
2003
GetCustomDataProperty(JSContext * cx,HandleObject obj,HandleId id,MutableHandleValue vp)2004 static bool GetCustomDataProperty(JSContext* cx, HandleObject obj, HandleId id,
2005 MutableHandleValue vp) {
2006 AutoCheckRecursionLimit recursion(cx);
2007 if (!recursion.check(cx)) {
2008 return false;
2009 }
2010
2011 cx->check(obj, id, vp);
2012
2013 const JSClass* clasp = obj->getClass();
2014 if (clasp == &ArrayObject::class_) {
2015 if (!ArrayLengthGetter(cx, obj, id, vp)) {
2016 return false;
2017 }
2018 } else if (clasp == &MappedArgumentsObject::class_) {
2019 if (!MappedArgGetter(cx, obj, id, vp)) {
2020 return false;
2021 }
2022 } else {
2023 MOZ_RELEASE_ASSERT(clasp == &UnmappedArgumentsObject::class_);
2024 if (!UnmappedArgGetter(cx, obj, id, vp)) {
2025 return false;
2026 }
2027 }
2028
2029 cx->check(vp);
2030 return true;
2031 }
2032
CallGetter(JSContext * cx,HandleNativeObject obj,HandleValue receiver,HandleId id,PropertyInfo prop,MutableHandleValue vp)2033 static inline bool CallGetter(JSContext* cx, HandleNativeObject obj,
2034 HandleValue receiver, HandleId id,
2035 PropertyInfo prop, MutableHandleValue vp) {
2036 MOZ_ASSERT(!prop.isDataProperty());
2037
2038 if (prop.isAccessorProperty()) {
2039 RootedValue getter(cx, obj->getGetterValue(prop));
2040 return js::CallGetter(cx, receiver, getter, vp);
2041 }
2042
2043 MOZ_ASSERT(prop.isCustomDataProperty());
2044
2045 return GetCustomDataProperty(cx, obj, id, vp);
2046 }
2047
2048 template <AllowGC allowGC>
GetExistingProperty(JSContext * cx,typename MaybeRooted<Value,allowGC>::HandleType receiver,typename MaybeRooted<NativeObject *,allowGC>::HandleType obj,typename MaybeRooted<jsid,allowGC>::HandleType id,PropertyInfo prop,typename MaybeRooted<Value,allowGC>::MutableHandleType vp)2049 static MOZ_ALWAYS_INLINE bool GetExistingProperty(
2050 JSContext* cx, typename MaybeRooted<Value, allowGC>::HandleType receiver,
2051 typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
2052 typename MaybeRooted<jsid, allowGC>::HandleType id, PropertyInfo prop,
2053 typename MaybeRooted<Value, allowGC>::MutableHandleType vp) {
2054 if (prop.isDataProperty()) {
2055 vp.set(obj->getSlot(prop.slot()));
2056 return true;
2057 }
2058
2059 vp.setUndefined();
2060
2061 if (!prop.isCustomDataProperty() && !obj->hasGetter(prop)) {
2062 return true;
2063 }
2064
2065 if constexpr (!allowGC) {
2066 return false;
2067 } else {
2068 return CallGetter(cx, obj, receiver, id, prop, vp);
2069 }
2070 }
2071
NativeGetExistingProperty(JSContext * cx,HandleObject receiver,HandleNativeObject obj,HandleId id,PropertyInfo prop,MutableHandleValue vp)2072 bool js::NativeGetExistingProperty(JSContext* cx, HandleObject receiver,
2073 HandleNativeObject obj, HandleId id,
2074 PropertyInfo prop, MutableHandleValue vp) {
2075 RootedValue receiverValue(cx, ObjectValue(*receiver));
2076 return GetExistingProperty<CanGC>(cx, receiverValue, obj, id, prop, vp);
2077 }
2078
2079 enum IsNameLookup { NotNameLookup = false, NameLookup = true };
2080
2081 /*
2082 * Finish getting the property `receiver[id]` after looking at every object on
2083 * the prototype chain and not finding any such property.
2084 *
2085 * Per the spec, this should just set the result to `undefined` and call it a
2086 * day. However this function also runs when we're evaluating an
2087 * expression that's an Identifier (that is, an unqualified name lookup),
2088 * so we need to figure out if that's what's happening and throw
2089 * a ReferenceError if so.
2090 */
GetNonexistentProperty(JSContext * cx,HandleId id,IsNameLookup nameLookup,MutableHandleValue vp)2091 static bool GetNonexistentProperty(JSContext* cx, HandleId id,
2092 IsNameLookup nameLookup,
2093 MutableHandleValue vp) {
2094 vp.setUndefined();
2095
2096 // If we are doing a name lookup, this is a ReferenceError.
2097 if (nameLookup) {
2098 ReportIsNotDefined(cx, id);
2099 return false;
2100 }
2101
2102 // Otherwise, just return |undefined|.
2103 return true;
2104 }
2105
2106 // The NoGC version of GetNonexistentProperty, present only to make types line
2107 // up.
GetNonexistentProperty(JSContext * cx,const jsid & id,IsNameLookup nameLookup,FakeMutableHandle<Value> vp)2108 bool GetNonexistentProperty(JSContext* cx, const jsid& id,
2109 IsNameLookup nameLookup,
2110 FakeMutableHandle<Value> vp) {
2111 return false;
2112 }
2113
GeneralizedGetProperty(JSContext * cx,HandleObject obj,HandleId id,HandleValue receiver,IsNameLookup nameLookup,MutableHandleValue vp)2114 static inline bool GeneralizedGetProperty(JSContext* cx, HandleObject obj,
2115 HandleId id, HandleValue receiver,
2116 IsNameLookup nameLookup,
2117 MutableHandleValue vp) {
2118 AutoCheckRecursionLimit recursion(cx);
2119 if (!recursion.check(cx)) {
2120 return false;
2121 }
2122 if (nameLookup) {
2123 // When nameLookup is true, GetProperty implements ES6 rev 34 (2015 Feb
2124 // 20) 8.1.1.2.6 GetBindingValue, with step 3 (the call to HasProperty)
2125 // and step 6 (the call to Get) fused so that only a single lookup is
2126 // needed.
2127 //
2128 // If we get here, we've reached a non-native object. Fall back on the
2129 // algorithm as specified, with two separate lookups. (Note that we
2130 // throw ReferenceErrors regardless of strictness, technically a bug.)
2131
2132 bool found;
2133 if (!HasProperty(cx, obj, id, &found)) {
2134 return false;
2135 }
2136 if (!found) {
2137 ReportIsNotDefined(cx, id);
2138 return false;
2139 }
2140 }
2141
2142 return GetProperty(cx, obj, receiver, id, vp);
2143 }
2144
GeneralizedGetProperty(JSContext * cx,JSObject * obj,jsid id,const Value & receiver,IsNameLookup nameLookup,FakeMutableHandle<Value> vp)2145 static inline bool GeneralizedGetProperty(JSContext* cx, JSObject* obj, jsid id,
2146 const Value& receiver,
2147 IsNameLookup nameLookup,
2148 FakeMutableHandle<Value> vp) {
2149 AutoCheckRecursionLimit recursion(cx);
2150 if (!recursion.checkDontReport(cx)) {
2151 return false;
2152 }
2153 if (nameLookup) {
2154 return false;
2155 }
2156 return GetPropertyNoGC(cx, obj, receiver, id, vp.address());
2157 }
2158
GetSparseElementHelper(JSContext * cx,HandleArrayObject obj,int32_t int_id,MutableHandleValue result)2159 bool js::GetSparseElementHelper(JSContext* cx, HandleArrayObject obj,
2160 int32_t int_id, MutableHandleValue result) {
2161 // Callers should have ensured that this object has a static prototype.
2162 MOZ_ASSERT(obj->hasStaticPrototype());
2163
2164 // Indexed properties can not exist on the prototype chain.
2165 MOZ_ASSERT_IF(obj->staticPrototype() != nullptr,
2166 !ObjectMayHaveExtraIndexedProperties(obj->staticPrototype()));
2167
2168 MOZ_ASSERT(INT_FITS_IN_JSID(int_id));
2169 RootedId id(cx, INT_TO_JSID(int_id));
2170
2171 uint32_t index;
2172 PropMap* map = obj->shape()->lookup(cx, id, &index);
2173 if (!map) {
2174 // Property not found, return directly.
2175 result.setUndefined();
2176 return true;
2177 }
2178
2179 PropertyInfo prop = map->getPropertyInfo(index);
2180 RootedValue receiver(cx, ObjectValue(*obj));
2181 return GetExistingProperty<CanGC>(cx, receiver, obj, id, prop, result);
2182 }
2183
2184 template <AllowGC allowGC>
NativeGetPropertyInline(JSContext * cx,typename MaybeRooted<NativeObject *,allowGC>::HandleType obj,typename MaybeRooted<Value,allowGC>::HandleType receiver,typename MaybeRooted<jsid,allowGC>::HandleType id,IsNameLookup nameLookup,typename MaybeRooted<Value,allowGC>::MutableHandleType vp)2185 static MOZ_ALWAYS_INLINE bool NativeGetPropertyInline(
2186 JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
2187 typename MaybeRooted<Value, allowGC>::HandleType receiver,
2188 typename MaybeRooted<jsid, allowGC>::HandleType id, IsNameLookup nameLookup,
2189 typename MaybeRooted<Value, allowGC>::MutableHandleType vp) {
2190 typename MaybeRooted<NativeObject*, allowGC>::RootType pobj(cx, obj);
2191 PropertyResult prop;
2192
2193 // This loop isn't explicit in the spec algorithm. See the comment on step
2194 // 4.d below.
2195 for (;;) {
2196 // Steps 2-3.
2197 if (!NativeLookupOwnPropertyInline<allowGC>(cx, pobj, id, &prop)) {
2198 return false;
2199 }
2200
2201 if (prop.isFound()) {
2202 // Steps 5-8. Special case for dense elements because
2203 // GetExistingProperty doesn't support those.
2204 if (prop.isDenseElement()) {
2205 vp.set(pobj->getDenseElement(prop.denseElementIndex()));
2206 return true;
2207 }
2208 if (prop.isTypedArrayElement()) {
2209 size_t idx = prop.typedArrayElementIndex();
2210 auto* tarr = &pobj->template as<TypedArrayObject>();
2211 return tarr->template getElement<allowGC>(cx, idx, vp);
2212 }
2213
2214 return GetExistingProperty<allowGC>(cx, receiver, pobj, id,
2215 prop.propertyInfo(), vp);
2216 }
2217
2218 // Steps 4.a-b.
2219 JSObject* proto = pobj->staticPrototype();
2220
2221 // Step 4.c. The spec algorithm simply returns undefined if proto is
2222 // null, but see the comment on GetNonexistentProperty.
2223 if (!proto || prop.shouldIgnoreProtoChain()) {
2224 return GetNonexistentProperty(cx, id, nameLookup, vp);
2225 }
2226
2227 // Step 4.d. If the prototype is also native, this step is a
2228 // recursive tail call, and we don't need to go through all the
2229 // plumbing of JSObject::getGeneric; the top of the loop is where
2230 // we're going to end up anyway. But if pobj is non-native,
2231 // that optimization would be incorrect.
2232 if (proto->getOpsGetProperty()) {
2233 RootedObject protoRoot(cx, proto);
2234 return GeneralizedGetProperty(cx, protoRoot, id, receiver, nameLookup,
2235 vp);
2236 }
2237
2238 pobj = &proto->as<NativeObject>();
2239 }
2240 }
2241
NativeGetProperty(JSContext * cx,HandleNativeObject obj,HandleValue receiver,HandleId id,MutableHandleValue vp)2242 bool js::NativeGetProperty(JSContext* cx, HandleNativeObject obj,
2243 HandleValue receiver, HandleId id,
2244 MutableHandleValue vp) {
2245 return NativeGetPropertyInline<CanGC>(cx, obj, receiver, id, NotNameLookup,
2246 vp);
2247 }
2248
NativeGetPropertyNoGC(JSContext * cx,NativeObject * obj,const Value & receiver,jsid id,Value * vp)2249 bool js::NativeGetPropertyNoGC(JSContext* cx, NativeObject* obj,
2250 const Value& receiver, jsid id, Value* vp) {
2251 AutoAssertNoPendingException noexc(cx);
2252 return NativeGetPropertyInline<NoGC>(cx, obj, receiver, id, NotNameLookup,
2253 vp);
2254 }
2255
NativeGetElement(JSContext * cx,HandleNativeObject obj,HandleValue receiver,int32_t index,MutableHandleValue vp)2256 bool js::NativeGetElement(JSContext* cx, HandleNativeObject obj,
2257 HandleValue receiver, int32_t index,
2258 MutableHandleValue vp) {
2259 RootedId id(cx);
2260
2261 if (MOZ_LIKELY(index >= 0)) {
2262 if (!IndexToId(cx, index, &id)) {
2263 return false;
2264 }
2265 } else {
2266 RootedValue indexVal(cx, Int32Value(index));
2267 if (!PrimitiveValueToId<CanGC>(cx, indexVal, &id)) {
2268 return false;
2269 }
2270 }
2271 return NativeGetProperty(cx, obj, receiver, id, vp);
2272 }
2273
GetNameBoundInEnvironment(JSContext * cx,HandleObject envArg,HandleId id,MutableHandleValue vp)2274 bool js::GetNameBoundInEnvironment(JSContext* cx, HandleObject envArg,
2275 HandleId id, MutableHandleValue vp) {
2276 // Manually unwrap 'with' environments to prevent looking up @@unscopables
2277 // twice.
2278 //
2279 // This is unfortunate because internally, the engine does not distinguish
2280 // HasProperty from HasBinding: both are implemented as a HasPropertyOp
2281 // hook on a WithEnvironmentObject.
2282 //
2283 // In the case of attempting to get the value of a binding already looked up
2284 // via JSOp::BindName, calling HasProperty on the WithEnvironmentObject is
2285 // equivalent to calling HasBinding a second time. This results in the
2286 // incorrect behavior of performing the @@unscopables check again.
2287 RootedObject env(cx, MaybeUnwrapWithEnvironment(envArg));
2288 RootedValue receiver(cx, ObjectValue(*env));
2289 if (env->getOpsGetProperty()) {
2290 return GeneralizedGetProperty(cx, env, id, receiver, NameLookup, vp);
2291 }
2292 return NativeGetPropertyInline<CanGC>(cx, env.as<NativeObject>(), receiver,
2293 id, NameLookup, vp);
2294 }
2295
2296 /*** [[Set]] ****************************************************************/
2297
SetCustomDataProperty(JSContext * cx,HandleObject obj,HandleId id,HandleValue v,ObjectOpResult & result)2298 static bool SetCustomDataProperty(JSContext* cx, HandleObject obj, HandleId id,
2299 HandleValue v, ObjectOpResult& result) {
2300 AutoCheckRecursionLimit recursion(cx);
2301 if (!recursion.check(cx)) {
2302 return false;
2303 }
2304
2305 cx->check(obj, id, v);
2306
2307 const JSClass* clasp = obj->getClass();
2308 if (clasp == &ArrayObject::class_) {
2309 return ArrayLengthSetter(cx, obj, id, v, result);
2310 }
2311 if (clasp == &MappedArgumentsObject::class_) {
2312 return MappedArgSetter(cx, obj, id, v, result);
2313 }
2314 MOZ_RELEASE_ASSERT(clasp == &UnmappedArgumentsObject::class_);
2315 return UnmappedArgSetter(cx, obj, id, v, result);
2316 }
2317
MaybeReportUndeclaredVarAssignment(JSContext * cx,HandleId id)2318 static bool MaybeReportUndeclaredVarAssignment(JSContext* cx, HandleId id) {
2319 {
2320 jsbytecode* pc;
2321 JSScript* script =
2322 cx->currentScript(&pc, JSContext::AllowCrossRealm::Allow);
2323 if (!script) {
2324 return true;
2325 }
2326
2327 if (!IsStrictSetPC(pc)) {
2328 return true;
2329 }
2330 }
2331
2332 UniqueChars bytes =
2333 IdToPrintableUTF8(cx, id, IdToPrintableBehavior::IdIsIdentifier);
2334 if (!bytes) {
2335 return false;
2336 }
2337 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_UNDECLARED_VAR,
2338 bytes.get());
2339 return false;
2340 }
2341
2342 /*
2343 * Finish assignment to a shapeful data property of a native object obj. This
2344 * conforms to no standard and there is a lot of legacy baggage here.
2345 */
NativeSetExistingDataProperty(JSContext * cx,HandleNativeObject obj,HandleId id,PropertyInfo prop,HandleValue v,ObjectOpResult & result)2346 static bool NativeSetExistingDataProperty(JSContext* cx, HandleNativeObject obj,
2347 HandleId id, PropertyInfo prop,
2348 HandleValue v,
2349 ObjectOpResult& result) {
2350 MOZ_ASSERT(obj->is<NativeObject>());
2351 MOZ_ASSERT(prop.isDataDescriptor());
2352
2353 if (prop.isDataProperty()) {
2354 // The common path. Standard data property.
2355 obj->setSlot(prop.slot(), v);
2356 return result.succeed();
2357 }
2358
2359 MOZ_ASSERT(prop.isCustomDataProperty());
2360 MOZ_ASSERT(!obj->is<WithEnvironmentObject>()); // See bug 1128681.
2361
2362 return SetCustomDataProperty(cx, obj, id, v, result);
2363 }
2364
2365 /*
2366 * When a [[Set]] operation finds no existing property with the given id
2367 * or finds a writable data property on the prototype chain, we end up here.
2368 * Finish the [[Set]] by defining a new property on receiver.
2369 *
2370 * This implements ES6 draft rev 28, 9.1.9 [[Set]] steps 5.b-f, but it
2371 * is really old code and there are a few barnacles.
2372 */
SetPropertyByDefining(JSContext * cx,HandleId id,HandleValue v,HandleValue receiverValue,ObjectOpResult & result)2373 bool js::SetPropertyByDefining(JSContext* cx, HandleId id, HandleValue v,
2374 HandleValue receiverValue,
2375 ObjectOpResult& result) {
2376 // Step 5.b.
2377 if (!receiverValue.isObject()) {
2378 return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER);
2379 }
2380 RootedObject receiver(cx, &receiverValue.toObject());
2381
2382 bool existing;
2383 {
2384 // Steps 5.c-d.
2385 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
2386 if (!GetOwnPropertyDescriptor(cx, receiver, id, &desc)) {
2387 return false;
2388 }
2389
2390 existing = desc.isSome();
2391
2392 // Step 5.e.
2393 if (existing) {
2394 // Step 5.e.i.
2395 if (desc->isAccessorDescriptor()) {
2396 return result.fail(JSMSG_OVERWRITING_ACCESSOR);
2397 }
2398
2399 // Step 5.e.ii.
2400 if (!desc->writable()) {
2401 return result.fail(JSMSG_READ_ONLY);
2402 }
2403 }
2404 }
2405
2406 // Purge the property cache of now-shadowed id in receiver's environment
2407 // chain.
2408 if (!ReshapeForShadowedProp(cx, receiver, id)) {
2409 return false;
2410 }
2411
2412 // Steps 5.e.iii-iv. and 5.f.i. Define the new data property.
2413 Rooted<PropertyDescriptor> desc(cx);
2414 if (existing) {
2415 desc = PropertyDescriptor::Empty();
2416 desc.setValue(v);
2417 } else {
2418 desc = PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
2419 JS::PropertyAttribute::Enumerable,
2420 JS::PropertyAttribute::Writable});
2421 }
2422 return DefineProperty(cx, receiver, id, desc, result);
2423 }
2424
2425 // When setting |id| for |receiver| and |obj| has no property for id, continue
2426 // the search up the prototype chain.
SetPropertyOnProto(JSContext * cx,HandleObject obj,HandleId id,HandleValue v,HandleValue receiver,ObjectOpResult & result)2427 bool js::SetPropertyOnProto(JSContext* cx, HandleObject obj, HandleId id,
2428 HandleValue v, HandleValue receiver,
2429 ObjectOpResult& result) {
2430 MOZ_ASSERT(!obj->is<ProxyObject>());
2431
2432 RootedObject proto(cx, obj->staticPrototype());
2433 if (proto) {
2434 return SetProperty(cx, proto, id, v, receiver, result);
2435 }
2436
2437 return SetPropertyByDefining(cx, id, v, receiver, result);
2438 }
2439
2440 /*
2441 * Implement "the rest of" assignment to a property when no property
2442 * receiver[id] was found anywhere on the prototype chain.
2443 *
2444 * FIXME: This should be updated to follow ES6 draft rev 28, section 9.1.9,
2445 * steps 4.d.i and 5.
2446 */
2447 template <QualifiedBool IsQualified>
SetNonexistentProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue v,HandleValue receiver,ObjectOpResult & result)2448 static bool SetNonexistentProperty(JSContext* cx, HandleNativeObject obj,
2449 HandleId id, HandleValue v,
2450 HandleValue receiver,
2451 ObjectOpResult& result) {
2452 if (!IsQualified && receiver.isObject() &&
2453 receiver.toObject().isUnqualifiedVarObj()) {
2454 if (!MaybeReportUndeclaredVarAssignment(cx, id)) {
2455 return false;
2456 }
2457 }
2458
2459 // Pure optimization for the common case. There's no point performing the
2460 // lookup in step 5.c again, as our caller just did it for us.
2461 if (IsQualified && receiver.isObject() && obj == &receiver.toObject()) {
2462 // Ensure that a custom GetOwnPropertyOp, if present, doesn't
2463 // introduce additional properties which weren't previously found by
2464 // LookupOwnProperty.
2465 #ifdef DEBUG
2466 if (GetOwnPropertyOp op = obj->getOpsGetOwnPropertyDescriptor()) {
2467 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
2468 if (!op(cx, obj, id, &desc)) {
2469 return false;
2470 }
2471 MOZ_ASSERT(desc.isNothing());
2472 }
2473 #endif
2474
2475 // Step 5.e. Define the new data property.
2476 if (DefinePropertyOp op = obj->getOpsDefineProperty()) {
2477 // Purge the property cache of now-shadowed id in receiver's environment
2478 // chain.
2479 if (!ReshapeForShadowedProp(cx, obj, id)) {
2480 return false;
2481 }
2482
2483 MOZ_ASSERT(!cx->isHelperThreadContext());
2484
2485 Rooted<PropertyDescriptor> desc(
2486 cx, PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
2487 JS::PropertyAttribute::Enumerable,
2488 JS::PropertyAttribute::Writable}));
2489 return op(cx, obj, id, desc, result);
2490 }
2491
2492 return DefineNonexistentProperty(cx, obj, id, v, result);
2493 }
2494
2495 return SetPropertyByDefining(cx, id, v, receiver, result);
2496 }
2497
2498 // Set an existing own property obj[index] that's a dense element.
SetDenseElement(JSContext * cx,HandleNativeObject obj,uint32_t index,HandleValue v,ObjectOpResult & result)2499 static bool SetDenseElement(JSContext* cx, HandleNativeObject obj,
2500 uint32_t index, HandleValue v,
2501 ObjectOpResult& result) {
2502 MOZ_ASSERT(!obj->is<TypedArrayObject>());
2503 MOZ_ASSERT(obj->containsDenseElement(index));
2504
2505 obj->setDenseElement(index, v);
2506 return result.succeed();
2507 }
2508
2509 /*
2510 * Finish the assignment `receiver[id] = v` when an existing property (shape)
2511 * has been found on a native object (pobj). This implements ES6 draft rev 32
2512 * (2015 Feb 2) 9.1.9 steps 5 and 6.
2513 *
2514 * It is necessary to pass both id and shape because shape could be an implicit
2515 * dense or typed array element (i.e. not actually a pointer to a Shape).
2516 */
SetExistingProperty(JSContext * cx,HandleId id,HandleValue v,HandleValue receiver,HandleNativeObject pobj,const PropertyResult & prop,ObjectOpResult & result)2517 static bool SetExistingProperty(JSContext* cx, HandleId id, HandleValue v,
2518 HandleValue receiver, HandleNativeObject pobj,
2519 const PropertyResult& prop,
2520 ObjectOpResult& result) {
2521 // Step 5 for dense elements.
2522 if (prop.isDenseElement() || prop.isTypedArrayElement()) {
2523 // Step 5.a.
2524 if (pobj->denseElementsAreFrozen()) {
2525 return result.fail(JSMSG_READ_ONLY);
2526 }
2527
2528 // Pure optimization for the common case:
2529 if (receiver.isObject() && pobj == &receiver.toObject()) {
2530 if (prop.isTypedArrayElement()) {
2531 Rooted<TypedArrayObject*> tobj(cx, &pobj->as<TypedArrayObject>());
2532 size_t idx = prop.typedArrayElementIndex();
2533 return SetTypedArrayElement(cx, tobj, idx, v, result);
2534 }
2535
2536 return SetDenseElement(cx, pobj, prop.denseElementIndex(), v, result);
2537 }
2538
2539 // Steps 5.b-f.
2540 return SetPropertyByDefining(cx, id, v, receiver, result);
2541 }
2542
2543 // Step 5 for all other properties.
2544 PropertyInfo propInfo = prop.propertyInfo();
2545 if (propInfo.isDataDescriptor()) {
2546 // Step 5.a.
2547 if (!propInfo.writable()) {
2548 return result.fail(JSMSG_READ_ONLY);
2549 }
2550
2551 // steps 5.c-f.
2552 if (receiver.isObject() && pobj == &receiver.toObject()) {
2553 // Pure optimization for the common case. There's no point performing
2554 // the lookup in step 5.c again, as our caller just did it for us. The
2555 // result is |shapeProp|.
2556
2557 // Steps 5.e.i-ii.
2558 return NativeSetExistingDataProperty(cx, pobj, id, propInfo, v, result);
2559 }
2560
2561 // Shadow pobj[id] by defining a new data property receiver[id].
2562 // Delegate everything to SetPropertyByDefining.
2563 return SetPropertyByDefining(cx, id, v, receiver, result);
2564 }
2565
2566 // Steps 6-11.
2567 MOZ_ASSERT(propInfo.isAccessorProperty());
2568
2569 JSObject* setterObject = pobj->getSetter(propInfo);
2570 if (!setterObject) {
2571 return result.fail(JSMSG_GETTER_ONLY);
2572 }
2573
2574 RootedValue setter(cx, ObjectValue(*setterObject));
2575 if (!js::CallSetter(cx, receiver, setter, v)) {
2576 return false;
2577 }
2578
2579 return result.succeed();
2580 }
2581
2582 template <QualifiedBool IsQualified>
NativeSetProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue v,HandleValue receiver,ObjectOpResult & result)2583 bool js::NativeSetProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
2584 HandleValue v, HandleValue receiver,
2585 ObjectOpResult& result) {
2586 // Step numbers below reference ES6 rev 27 9.1.9, the [[Set]] internal
2587 // method for ordinary objects. We substitute our own names for these names
2588 // used in the spec: O -> pobj, P -> id, ownDesc -> shape.
2589 PropertyResult prop;
2590 RootedNativeObject pobj(cx, obj);
2591
2592 // This loop isn't explicit in the spec algorithm. See the comment on step
2593 // 4.c.i below. (There's a very similar loop in the NativeGetProperty
2594 // implementation, but unfortunately not similar enough to common up.)
2595 //
2596 // We're intentionally not spec-compliant for TypedArrays:
2597 // When |pobj| is a TypedArray and |id| is a TypedArray index, we should
2598 // ignore |receiver| and instead always try to set the property on |pobj|.
2599 // Bug 1502889 showed that this behavior isn't web-compatible. This issue is
2600 // also reported at <https://github.com/tc39/ecma262/issues/1541>.
2601 for (;;) {
2602 // Steps 2-3.
2603 if (!NativeLookupOwnPropertyInline<CanGC>(cx, pobj, id, &prop)) {
2604 return false;
2605 }
2606
2607 if (prop.isFound()) {
2608 // Steps 5-6.
2609 return SetExistingProperty(cx, id, v, receiver, pobj, prop, result);
2610 }
2611
2612 // Steps 4.a-b.
2613 // As a side-effect of NativeLookupOwnPropertyInline, we may determine that
2614 // a property is not found and the proto chain should not be searched. This
2615 // can occur for:
2616 // - Out-of-range numeric properties of a TypedArrayObject
2617 // - Recursive resolve hooks (which is expected when they try to set the
2618 // property being resolved).
2619 JSObject* proto = pobj->staticPrototype();
2620 if (!proto || prop.shouldIgnoreProtoChain()) {
2621 // Step 4.d.i (and step 5).
2622 return SetNonexistentProperty<IsQualified>(cx, obj, id, v, receiver,
2623 result);
2624 }
2625
2626 // Step 4.c.i. If the prototype is also native, this step is a
2627 // recursive tail call, and we don't need to go through all the
2628 // plumbing of SetProperty; the top of the loop is where we're going to
2629 // end up anyway. But if pobj is non-native, that optimization would be
2630 // incorrect.
2631 if (!proto->is<NativeObject>()) {
2632 // Unqualified assignments are not specified to go through [[Set]]
2633 // at all, but they do go through this function. So check for
2634 // unqualified assignment to a nonexistent global (a strict error).
2635 RootedObject protoRoot(cx, proto);
2636 if (!IsQualified) {
2637 bool found;
2638 if (!HasProperty(cx, protoRoot, id, &found)) {
2639 return false;
2640 }
2641 if (!found) {
2642 return SetNonexistentProperty<IsQualified>(cx, obj, id, v, receiver,
2643 result);
2644 }
2645 }
2646
2647 return SetProperty(cx, protoRoot, id, v, receiver, result);
2648 }
2649 pobj = &proto->as<NativeObject>();
2650 }
2651 }
2652
2653 template bool js::NativeSetProperty<Qualified>(JSContext* cx,
2654 HandleNativeObject obj,
2655 HandleId id, HandleValue value,
2656 HandleValue receiver,
2657 ObjectOpResult& result);
2658
2659 template bool js::NativeSetProperty<Unqualified>(JSContext* cx,
2660 HandleNativeObject obj,
2661 HandleId id, HandleValue value,
2662 HandleValue receiver,
2663 ObjectOpResult& result);
2664
NativeSetElement(JSContext * cx,HandleNativeObject obj,uint32_t index,HandleValue v,HandleValue receiver,ObjectOpResult & result)2665 bool js::NativeSetElement(JSContext* cx, HandleNativeObject obj, uint32_t index,
2666 HandleValue v, HandleValue receiver,
2667 ObjectOpResult& result) {
2668 RootedId id(cx);
2669 if (!IndexToId(cx, index, &id)) {
2670 return false;
2671 }
2672 return NativeSetProperty<Qualified>(cx, obj, id, v, receiver, result);
2673 }
2674
2675 /*** [[Delete]] *************************************************************/
2676
CallJSDeletePropertyOp(JSContext * cx,JSDeletePropertyOp op,HandleObject receiver,HandleId id,ObjectOpResult & result)2677 static bool CallJSDeletePropertyOp(JSContext* cx, JSDeletePropertyOp op,
2678 HandleObject receiver, HandleId id,
2679 ObjectOpResult& result) {
2680 AutoCheckRecursionLimit recursion(cx);
2681 if (!recursion.check(cx)) {
2682 return false;
2683 }
2684
2685 cx->check(receiver, id);
2686 if (op) {
2687 return op(cx, receiver, id, result);
2688 }
2689 return result.succeed();
2690 }
2691
2692 // ES6 draft rev31 9.1.10 [[Delete]]
NativeDeleteProperty(JSContext * cx,HandleNativeObject obj,HandleId id,ObjectOpResult & result)2693 bool js::NativeDeleteProperty(JSContext* cx, HandleNativeObject obj,
2694 HandleId id, ObjectOpResult& result) {
2695 // Steps 2-3.
2696 PropertyResult prop;
2697 if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &prop)) {
2698 return false;
2699 }
2700
2701 // Step 4.
2702 if (prop.isNotFound()) {
2703 // If no property call the class's delProperty hook, passing succeeded
2704 // as the result parameter. This always succeeds when there is no hook.
2705 return CallJSDeletePropertyOp(cx, obj->getClass()->getDelProperty(), obj,
2706 id, result);
2707 }
2708
2709 // Step 6. Non-configurable property.
2710 if (!GetPropertyAttributes(obj, prop).configurable()) {
2711 return result.failCantDelete();
2712 }
2713
2714 // Typed array elements are configurable, but can't be deleted.
2715 if (prop.isTypedArrayElement()) {
2716 return result.failCantDelete();
2717 }
2718
2719 if (!CallJSDeletePropertyOp(cx, obj->getClass()->getDelProperty(), obj, id,
2720 result)) {
2721 return false;
2722 }
2723 if (!result) {
2724 return true;
2725 }
2726
2727 // Step 5.
2728 if (prop.isDenseElement()) {
2729 obj->setDenseElementHole(prop.denseElementIndex());
2730 } else {
2731 if (!NativeObject::removeProperty(cx, obj, id)) {
2732 return false;
2733 }
2734 }
2735
2736 return SuppressDeletedProperty(cx, obj, id);
2737 }
2738
CopyDataPropertiesNative(JSContext * cx,HandlePlainObject target,HandleNativeObject from,Handle<PlainObject * > excludedItems,bool * optimized)2739 bool js::CopyDataPropertiesNative(JSContext* cx, HandlePlainObject target,
2740 HandleNativeObject from,
2741 Handle<PlainObject*> excludedItems,
2742 bool* optimized) {
2743 MOZ_ASSERT(
2744 !target->isUsedAsPrototype(),
2745 "CopyDataPropertiesNative should only be called during object literal "
2746 "construction"
2747 "which precludes that |target| is the prototype of any other object");
2748
2749 *optimized = false;
2750
2751 // Don't use the fast path if |from| may have extra indexed or lazy
2752 // properties.
2753 if (from->getDenseInitializedLength() > 0 || from->isIndexed() ||
2754 from->is<TypedArrayObject>() || from->getClass()->getNewEnumerate() ||
2755 from->getClass()->getEnumerate()) {
2756 return true;
2757 }
2758
2759 // Collect all enumerable data properties.
2760 Rooted<PropertyInfoWithKeyVector> props(cx, PropertyInfoWithKeyVector(cx));
2761
2762 RootedShape fromShape(cx, from->shape());
2763 for (ShapePropertyIter<NoGC> iter(fromShape); !iter.done(); iter++) {
2764 jsid id = iter->key();
2765 MOZ_ASSERT(!JSID_IS_INT(id));
2766
2767 if (!iter->enumerable()) {
2768 continue;
2769 }
2770 if (excludedItems && excludedItems->contains(cx, id)) {
2771 continue;
2772 }
2773
2774 // Don't use the fast path if |from| contains non-data properties.
2775 //
2776 // This enables two optimizations:
2777 // 1. We don't need to handle the case when accessors modify |from|.
2778 // 2. String and symbol properties can be added in one go.
2779 if (!iter->isDataProperty()) {
2780 return true;
2781 }
2782
2783 if (!props.append(*iter)) {
2784 return false;
2785 }
2786 }
2787
2788 *optimized = true;
2789
2790 // If |target| contains no own properties, we can directly call
2791 // AddDataPropertyNonPrototype.
2792 const bool targetHadNoOwnProperties = target->empty();
2793
2794 RootedId key(cx);
2795 RootedValue value(cx);
2796 for (size_t i = props.length(); i > 0; i--) {
2797 PropertyInfoWithKey prop = props[i - 1];
2798 MOZ_ASSERT(prop.isDataProperty());
2799 MOZ_ASSERT(prop.enumerable());
2800
2801 key = prop.key();
2802 MOZ_ASSERT(!JSID_IS_INT(key));
2803
2804 MOZ_ASSERT(from->is<NativeObject>());
2805 MOZ_ASSERT(from->shape() == fromShape);
2806
2807 value = from->getSlot(prop.slot());
2808 if (targetHadNoOwnProperties) {
2809 MOZ_ASSERT(!target->containsPure(key),
2810 "didn't expect to find an existing property");
2811
2812 if (!AddDataPropertyNonPrototype(cx, target, key, value)) {
2813 return false;
2814 }
2815 } else {
2816 if (!NativeDefineDataProperty(cx, target, key, value, JSPROP_ENUMERATE)) {
2817 return false;
2818 }
2819 }
2820 }
2821
2822 return true;
2823 }
2824