1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
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/ArrayUtils.h"
10 #include "mozilla/Casting.h"
11
12 #include "jswatchpoint.h"
13
14 #include "gc/Marking.h"
15 #include "js/Value.h"
16 #include "vm/Debugger.h"
17 #include "vm/TypedArrayCommon.h"
18
19 #include "jsobjinlines.h"
20
21 #include "gc/Nursery-inl.h"
22 #include "vm/ArrayObject-inl.h"
23 #include "vm/EnvironmentObject-inl.h"
24 #include "vm/Shape-inl.h"
25
26 using namespace js;
27
28 using JS::AutoCheckCannotGC;
29 using JS::GenericNaN;
30 using mozilla::ArrayLength;
31 using mozilla::DebugOnly;
32 using mozilla::PodCopy;
33 using mozilla::RoundUpPow2;
34
35 static const ObjectElements emptyElementsHeader(0, 0);
36
37 /* Objects with no elements share one empty set of elements. */
38 HeapSlot* const js::emptyObjectElements =
39 reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeader) + sizeof(ObjectElements));
40
41 static const ObjectElements emptyElementsHeaderShared(0, 0, ObjectElements::SharedMemory::IsShared);
42
43 /* Objects with no elements share one empty set of elements. */
44 HeapSlot* const js::emptyObjectElementsShared =
45 reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeaderShared) + sizeof(ObjectElements));
46
47
48 #ifdef DEBUG
49
50 bool
canHaveNonEmptyElements()51 NativeObject::canHaveNonEmptyElements()
52 {
53 return !this->is<TypedArrayObject>();
54 }
55
56 #endif // DEBUG
57
58 /* static */ bool
ConvertElementsToDoubles(JSContext * cx,uintptr_t elementsPtr)59 ObjectElements::ConvertElementsToDoubles(JSContext* cx, uintptr_t elementsPtr)
60 {
61 /*
62 * This function is infallible, but has a fallible interface so that it can
63 * be called directly from Ion code. Only arrays can have their dense
64 * elements converted to doubles, and arrays never have empty elements.
65 */
66 HeapSlot* elementsHeapPtr = (HeapSlot*) elementsPtr;
67 MOZ_ASSERT(elementsHeapPtr != emptyObjectElements &&
68 elementsHeapPtr != emptyObjectElementsShared);
69
70 ObjectElements* header = ObjectElements::fromElements(elementsHeapPtr);
71 MOZ_ASSERT(!header->shouldConvertDoubleElements());
72
73 // Note: the elements can be mutated in place even for copy on write
74 // arrays. See comment on ObjectElements.
75 Value* vp = (Value*) elementsPtr;
76 for (size_t i = 0; i < header->initializedLength; i++) {
77 if (vp[i].isInt32())
78 vp[i].setDouble(vp[i].toInt32());
79 }
80
81 header->setShouldConvertDoubleElements();
82 return true;
83 }
84
85 /* static */ bool
MakeElementsCopyOnWrite(ExclusiveContext * cx,NativeObject * obj)86 ObjectElements::MakeElementsCopyOnWrite(ExclusiveContext* cx, NativeObject* obj)
87 {
88 static_assert(sizeof(HeapSlot) >= sizeof(GCPtrObject),
89 "there must be enough room for the owner object pointer at "
90 "the end of the elements");
91 if (!obj->ensureElements(cx, obj->getDenseInitializedLength() + 1))
92 return false;
93
94 ObjectElements* header = obj->getElementsHeader();
95
96 // Note: this method doesn't update type information to indicate that the
97 // elements might be copy on write. Handling this is left to the caller.
98 MOZ_ASSERT(!header->isCopyOnWrite());
99 MOZ_ASSERT(!header->isFrozen());
100 header->flags |= COPY_ON_WRITE;
101
102 header->ownerObject().init(obj);
103 return true;
104 }
105
106 /* static */ bool
FreezeElements(ExclusiveContext * cx,HandleNativeObject obj)107 ObjectElements::FreezeElements(ExclusiveContext* cx, HandleNativeObject obj)
108 {
109 if (!obj->maybeCopyElementsForWrite(cx))
110 return false;
111
112 if (obj->hasEmptyElements())
113 return true;
114
115 ObjectElements* header = obj->getElementsHeader();
116
117 // Note: this method doesn't update type information to indicate that the
118 // elements might be frozen. Handling this is left to the caller.
119 header->freeze();
120
121 return true;
122 }
123
124 #ifdef DEBUG
125 void
checkShapeConsistency()126 js::NativeObject::checkShapeConsistency()
127 {
128 static int throttle = -1;
129 if (throttle < 0) {
130 if (const char* var = getenv("JS_CHECK_SHAPE_THROTTLE"))
131 throttle = atoi(var);
132 if (throttle < 0)
133 throttle = 0;
134 }
135 if (throttle == 0)
136 return;
137
138 MOZ_ASSERT(isNative());
139
140 Shape* shape = lastProperty();
141 Shape* prev = nullptr;
142
143 AutoCheckCannotGC nogc;
144 if (inDictionaryMode()) {
145 if (ShapeTable* table = shape->maybeTable(nogc)) {
146 for (uint32_t fslot = table->freeList();
147 fslot != SHAPE_INVALID_SLOT;
148 fslot = getSlot(fslot).toPrivateUint32())
149 {
150 MOZ_ASSERT(fslot < slotSpan());
151 }
152
153 for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) {
154 MOZ_ASSERT_IF(lastProperty() != shape, !shape->hasTable());
155
156 ShapeTable::Entry& entry = table->search<MaybeAdding::NotAdding>(shape->propid(),
157 nogc);
158 MOZ_ASSERT(entry.shape() == shape);
159 }
160 }
161
162 shape = lastProperty();
163 for (int n = throttle; --n >= 0 && shape; shape = shape->parent) {
164 MOZ_ASSERT_IF(shape->slot() != SHAPE_INVALID_SLOT, shape->slot() < slotSpan());
165 if (!prev) {
166 MOZ_ASSERT(lastProperty() == shape);
167 MOZ_ASSERT(shape->listp == &shape_);
168 } else {
169 MOZ_ASSERT(shape->listp == &prev->parent);
170 }
171 prev = shape;
172 }
173 } else {
174 for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) {
175 if (ShapeTable* table = shape->maybeTable(nogc)) {
176 MOZ_ASSERT(shape->parent);
177 for (Shape::Range<NoGC> r(shape); !r.empty(); r.popFront()) {
178 ShapeTable::Entry& entry =
179 table->search<MaybeAdding::NotAdding>(r.front().propid(), nogc);
180 MOZ_ASSERT(entry.shape() == &r.front());
181 }
182 }
183 if (prev) {
184 MOZ_ASSERT(prev->maybeSlot() >= shape->maybeSlot());
185 shape->kids.checkConsistency(prev);
186 }
187 prev = shape;
188 }
189 }
190 }
191 #endif
192
193 void
initializeSlotRange(uint32_t start,uint32_t length)194 js::NativeObject::initializeSlotRange(uint32_t start, uint32_t length)
195 {
196 /*
197 * No bounds check, as this is used when the object's shape does not
198 * reflect its allocated slots (updateSlotsForSpan).
199 */
200 HeapSlot* fixedStart;
201 HeapSlot* fixedEnd;
202 HeapSlot* slotsStart;
203 HeapSlot* slotsEnd;
204 getSlotRangeUnchecked(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
205
206 uint32_t offset = start;
207 for (HeapSlot* sp = fixedStart; sp < fixedEnd; sp++)
208 sp->init(this, HeapSlot::Slot, offset++, UndefinedValue());
209 for (HeapSlot* sp = slotsStart; sp < slotsEnd; sp++)
210 sp->init(this, HeapSlot::Slot, offset++, UndefinedValue());
211 }
212
213 void
initSlotRange(uint32_t start,const Value * vector,uint32_t length)214 js::NativeObject::initSlotRange(uint32_t start, const Value* vector, uint32_t length)
215 {
216 HeapSlot* fixedStart;
217 HeapSlot* fixedEnd;
218 HeapSlot* slotsStart;
219 HeapSlot* slotsEnd;
220 getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
221 for (HeapSlot* sp = fixedStart; sp < fixedEnd; sp++)
222 sp->init(this, HeapSlot::Slot, start++, *vector++);
223 for (HeapSlot* sp = slotsStart; sp < slotsEnd; sp++)
224 sp->init(this, HeapSlot::Slot, start++, *vector++);
225 }
226
227 void
copySlotRange(uint32_t start,const Value * vector,uint32_t length)228 js::NativeObject::copySlotRange(uint32_t start, const Value* vector, uint32_t length)
229 {
230 HeapSlot* fixedStart;
231 HeapSlot* fixedEnd;
232 HeapSlot* slotsStart;
233 HeapSlot* slotsEnd;
234 getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
235 for (HeapSlot* sp = fixedStart; sp < fixedEnd; sp++)
236 sp->set(this, HeapSlot::Slot, start++, *vector++);
237 for (HeapSlot* sp = slotsStart; sp < slotsEnd; sp++)
238 sp->set(this, HeapSlot::Slot, start++, *vector++);
239 }
240
241 #ifdef DEBUG
242 bool
slotInRange(uint32_t slot,SentinelAllowed sentinel) const243 js::NativeObject::slotInRange(uint32_t slot, SentinelAllowed sentinel) const
244 {
245 uint32_t capacity = numFixedSlots() + numDynamicSlots();
246 if (sentinel == SENTINEL_ALLOWED)
247 return slot <= capacity;
248 return slot < capacity;
249 }
250 #endif /* DEBUG */
251
252 Shape*
lookup(ExclusiveContext * cx,jsid id)253 js::NativeObject::lookup(ExclusiveContext* cx, jsid id)
254 {
255 MOZ_ASSERT(isNative());
256 return Shape::search(cx, lastProperty(), id);
257 }
258
259 Shape*
lookupPure(jsid id)260 js::NativeObject::lookupPure(jsid id)
261 {
262 MOZ_ASSERT(isNative());
263 return Shape::searchNoHashify(lastProperty(), id);
264 }
265
266 uint32_t
numFixedSlotsForCompilation() const267 js::NativeObject::numFixedSlotsForCompilation() const
268 {
269 // This is an alternative method for getting the number of fixed slots in an
270 // object. It requires more logic and memory accesses than numFixedSlots()
271 // but is safe to be called from the compilation thread, even if the main
272 // thread is actively mutating the VM.
273
274 // The compiler does not have access to nursery things.
275 MOZ_ASSERT(!IsInsideNursery(this));
276
277 if (this->is<ArrayObject>())
278 return 0;
279
280 gc::AllocKind kind = asTenured().getAllocKind();
281 return gc::GetGCKindSlots(kind, getClass());
282 }
283
284 uint32_t
dynamicSlotsCount(uint32_t nfixed,uint32_t span,const Class * clasp)285 js::NativeObject::dynamicSlotsCount(uint32_t nfixed, uint32_t span, const Class* clasp)
286 {
287 if (span <= nfixed)
288 return 0;
289 span -= nfixed;
290
291 // Increase the slots to SLOT_CAPACITY_MIN to decrease the likelihood
292 // the dynamic slots need to get increased again. ArrayObjects ignore
293 // this because slots are uncommon in that case.
294 if (clasp != &ArrayObject::class_ && span <= SLOT_CAPACITY_MIN)
295 return SLOT_CAPACITY_MIN;
296
297 uint32_t slots = mozilla::RoundUpPow2(span);
298 MOZ_ASSERT(slots >= span);
299 return slots;
300 }
301
302 inline bool
updateSlotsForSpan(ExclusiveContext * cx,size_t oldSpan,size_t newSpan)303 NativeObject::updateSlotsForSpan(ExclusiveContext* cx, size_t oldSpan, size_t newSpan)
304 {
305 MOZ_ASSERT(oldSpan != newSpan);
306
307 size_t oldCount = dynamicSlotsCount(numFixedSlots(), oldSpan, getClass());
308 size_t newCount = dynamicSlotsCount(numFixedSlots(), newSpan, getClass());
309
310 if (oldSpan < newSpan) {
311 if (oldCount < newCount && !growSlots(cx, oldCount, newCount))
312 return false;
313
314 if (newSpan == oldSpan + 1)
315 initSlotUnchecked(oldSpan, UndefinedValue());
316 else
317 initializeSlotRange(oldSpan, newSpan - oldSpan);
318 } else {
319 /* Trigger write barriers on the old slots before reallocating. */
320 prepareSlotRangeForOverwrite(newSpan, oldSpan);
321 invalidateSlotRange(newSpan, oldSpan - newSpan);
322
323 if (oldCount > newCount)
324 shrinkSlots(cx, oldCount, newCount);
325 }
326
327 return true;
328 }
329
330 bool
setLastProperty(ExclusiveContext * cx,Shape * shape)331 NativeObject::setLastProperty(ExclusiveContext* cx, Shape* shape)
332 {
333 MOZ_ASSERT(!inDictionaryMode());
334 MOZ_ASSERT(!shape->inDictionary());
335 MOZ_ASSERT(shape->zone() == zone());
336 MOZ_ASSERT(shape->numFixedSlots() == numFixedSlots());
337 MOZ_ASSERT(shape->getObjectClass() == getClass());
338
339 size_t oldSpan = lastProperty()->slotSpan();
340 size_t newSpan = shape->slotSpan();
341
342 if (oldSpan == newSpan) {
343 shape_ = shape;
344 return true;
345 }
346
347 if (!updateSlotsForSpan(cx, oldSpan, newSpan))
348 return false;
349
350 shape_ = shape;
351 return true;
352 }
353
354 void
setLastPropertyShrinkFixedSlots(Shape * shape)355 NativeObject::setLastPropertyShrinkFixedSlots(Shape* shape)
356 {
357 MOZ_ASSERT(!inDictionaryMode());
358 MOZ_ASSERT(!shape->inDictionary());
359 MOZ_ASSERT(shape->zone() == zone());
360 MOZ_ASSERT(lastProperty()->slotSpan() == shape->slotSpan());
361 MOZ_ASSERT(shape->getObjectClass() == getClass());
362
363 DebugOnly<size_t> oldFixed = numFixedSlots();
364 DebugOnly<size_t> newFixed = shape->numFixedSlots();
365 MOZ_ASSERT(newFixed < oldFixed);
366 MOZ_ASSERT(shape->slotSpan() <= oldFixed);
367 MOZ_ASSERT(shape->slotSpan() <= newFixed);
368 MOZ_ASSERT(dynamicSlotsCount(oldFixed, shape->slotSpan(), getClass()) == 0);
369 MOZ_ASSERT(dynamicSlotsCount(newFixed, shape->slotSpan(), getClass()) == 0);
370
371 shape_ = shape;
372 }
373
374 void
setLastPropertyMakeNonNative(Shape * shape)375 NativeObject::setLastPropertyMakeNonNative(Shape* shape)
376 {
377 MOZ_ASSERT(!inDictionaryMode());
378 MOZ_ASSERT(!shape->getObjectClass()->isNative());
379 MOZ_ASSERT(shape->zone() == zone());
380 MOZ_ASSERT(shape->slotSpan() == 0);
381 MOZ_ASSERT(shape->numFixedSlots() == 0);
382
383 if (hasDynamicElements())
384 js_free(getElementsHeader());
385 if (hasDynamicSlots()) {
386 js_free(slots_);
387 slots_ = nullptr;
388 }
389
390 shape_ = shape;
391 }
392
393 void
setLastPropertyMakeNative(ExclusiveContext * cx,Shape * shape)394 NativeObject::setLastPropertyMakeNative(ExclusiveContext* cx, Shape* shape)
395 {
396 MOZ_ASSERT(getClass()->isNative());
397 MOZ_ASSERT(shape->getObjectClass()->isNative());
398 MOZ_ASSERT(!shape->inDictionary());
399
400 // This method is used to convert unboxed objects into native objects. In
401 // this case, the shape_ field was previously used to store other data and
402 // this should be treated as an initialization.
403 shape_.init(shape);
404
405 slots_ = nullptr;
406 elements_ = emptyObjectElements;
407
408 size_t oldSpan = shape->numFixedSlots();
409 size_t newSpan = shape->slotSpan();
410
411 initializeSlotRange(0, oldSpan);
412
413 // A failure at this point will leave the object as a mutant, and we
414 // can't recover.
415 AutoEnterOOMUnsafeRegion oomUnsafe;
416 if (oldSpan != newSpan && !updateSlotsForSpan(cx, oldSpan, newSpan))
417 oomUnsafe.crash("NativeObject::setLastPropertyMakeNative");
418 }
419
420 bool
setSlotSpan(ExclusiveContext * cx,uint32_t span)421 NativeObject::setSlotSpan(ExclusiveContext* cx, uint32_t span)
422 {
423 MOZ_ASSERT(inDictionaryMode());
424
425 size_t oldSpan = lastProperty()->base()->slotSpan();
426 if (oldSpan == span)
427 return true;
428
429 if (!updateSlotsForSpan(cx, oldSpan, span))
430 return false;
431
432 lastProperty()->base()->setSlotSpan(span);
433 return true;
434 }
435
436 bool
growSlots(ExclusiveContext * cx,uint32_t oldCount,uint32_t newCount)437 NativeObject::growSlots(ExclusiveContext* cx, uint32_t oldCount, uint32_t newCount)
438 {
439 MOZ_ASSERT(newCount > oldCount);
440 MOZ_ASSERT_IF(!is<ArrayObject>(), newCount >= SLOT_CAPACITY_MIN);
441
442 /*
443 * Slot capacities are determined by the span of allocated objects. Due to
444 * the limited number of bits to store shape slots, object growth is
445 * throttled well before the slot capacity can overflow.
446 */
447 NativeObject::slotsSizeMustNotOverflow();
448 MOZ_ASSERT(newCount <= MAX_SLOTS_COUNT);
449
450 if (!oldCount) {
451 MOZ_ASSERT(!slots_);
452 slots_ = AllocateObjectBuffer<HeapSlot>(cx, this, newCount);
453 if (!slots_)
454 return false;
455 Debug_SetSlotRangeToCrashOnTouch(slots_, newCount);
456 return true;
457 }
458
459 HeapSlot* newslots = ReallocateObjectBuffer<HeapSlot>(cx, this, slots_, oldCount, newCount);
460 if (!newslots)
461 return false; /* Leave slots at its old size. */
462
463 slots_ = newslots;
464
465 Debug_SetSlotRangeToCrashOnTouch(slots_ + oldCount, newCount - oldCount);
466
467 return true;
468 }
469
470 /* static */ bool
growSlotsDontReportOOM(ExclusiveContext * cx,NativeObject * obj,uint32_t newCount)471 NativeObject::growSlotsDontReportOOM(ExclusiveContext* cx, NativeObject* obj, uint32_t newCount)
472 {
473 if (!obj->growSlots(cx, obj->numDynamicSlots(), newCount)) {
474 cx->recoverFromOutOfMemory();
475 return false;
476 }
477 return true;
478 }
479
480 static void
FreeSlots(ExclusiveContext * cx,HeapSlot * slots)481 FreeSlots(ExclusiveContext* cx, HeapSlot* slots)
482 {
483 // Note: threads without a JSContext do not have access to GGC nursery allocated things.
484 if (cx->isJSContext())
485 return cx->asJSContext()->runtime()->gc.nursery.freeBuffer(slots);
486 js_free(slots);
487 }
488
489 void
shrinkSlots(ExclusiveContext * cx,uint32_t oldCount,uint32_t newCount)490 NativeObject::shrinkSlots(ExclusiveContext* cx, uint32_t oldCount, uint32_t newCount)
491 {
492 MOZ_ASSERT(newCount < oldCount);
493
494 if (newCount == 0) {
495 FreeSlots(cx, slots_);
496 slots_ = nullptr;
497 return;
498 }
499
500 MOZ_ASSERT_IF(!is<ArrayObject>(), newCount >= SLOT_CAPACITY_MIN);
501
502 HeapSlot* newslots = ReallocateObjectBuffer<HeapSlot>(cx, this, slots_, oldCount, newCount);
503 if (!newslots) {
504 cx->recoverFromOutOfMemory();
505 return; /* Leave slots at its old size. */
506 }
507
508 slots_ = newslots;
509 }
510
511 /* static */ bool
sparsifyDenseElement(ExclusiveContext * cx,HandleNativeObject obj,uint32_t index)512 NativeObject::sparsifyDenseElement(ExclusiveContext* cx, HandleNativeObject obj, uint32_t index)
513 {
514 if (!obj->maybeCopyElementsForWrite(cx))
515 return false;
516
517 RootedValue value(cx, obj->getDenseElement(index));
518 MOZ_ASSERT(!value.isMagic(JS_ELEMENTS_HOLE));
519
520 removeDenseElementForSparseIndex(cx, obj, index);
521
522 uint32_t slot = obj->slotSpan();
523
524 RootedId id(cx, INT_TO_JSID(index));
525
526 AutoKeepShapeTables keep(cx);
527 ShapeTable::Entry* entry = nullptr;
528 if (obj->inDictionaryMode()) {
529 ShapeTable* table = obj->lastProperty()->ensureTableForDictionary(cx, keep);
530 if (!table)
531 return false;
532 entry = &table->search<MaybeAdding::Adding>(id, keep);
533 }
534
535 // NOTE: We don't use addDataProperty because we don't want the
536 // extensibility check if we're, for example, sparsifying frozen objects..
537 if (!addPropertyInternal(cx, obj, id, nullptr, nullptr, slot,
538 obj->getElementsHeader()->elementAttributes(),
539 0, entry, true, keep)) {
540 obj->setDenseElementUnchecked(index, value);
541 return false;
542 }
543
544 MOZ_ASSERT(slot == obj->slotSpan() - 1);
545 obj->initSlot(slot, value);
546
547 return true;
548 }
549
550 /* static */ bool
sparsifyDenseElements(js::ExclusiveContext * cx,HandleNativeObject obj)551 NativeObject::sparsifyDenseElements(js::ExclusiveContext* cx, HandleNativeObject obj)
552 {
553 if (!obj->maybeCopyElementsForWrite(cx))
554 return false;
555
556 uint32_t initialized = obj->getDenseInitializedLength();
557
558 /* Create new properties with the value of non-hole dense elements. */
559 for (uint32_t i = 0; i < initialized; i++) {
560 if (obj->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE))
561 continue;
562
563 if (!sparsifyDenseElement(cx, obj, i))
564 return false;
565 }
566
567 if (initialized)
568 obj->setDenseInitializedLengthUnchecked(0);
569
570 /*
571 * Reduce storage for dense elements which are now holes. Explicitly mark
572 * the elements capacity as zero, so that any attempts to add dense
573 * elements will be caught in ensureDenseElements.
574 */
575 if (obj->getDenseCapacity()) {
576 obj->shrinkElements(cx, 0);
577 obj->getElementsHeader()->capacity = 0;
578 }
579
580 return true;
581 }
582
583 bool
willBeSparseElements(uint32_t requiredCapacity,uint32_t newElementsHint)584 NativeObject::willBeSparseElements(uint32_t requiredCapacity, uint32_t newElementsHint)
585 {
586 MOZ_ASSERT(isNative());
587 MOZ_ASSERT(requiredCapacity > MIN_SPARSE_INDEX);
588
589 uint32_t cap = getDenseCapacity();
590 MOZ_ASSERT(requiredCapacity >= cap);
591
592 if (requiredCapacity > MAX_DENSE_ELEMENTS_COUNT)
593 return true;
594
595 uint32_t minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO;
596 if (newElementsHint >= minimalDenseCount)
597 return false;
598 minimalDenseCount -= newElementsHint;
599
600 if (minimalDenseCount > cap)
601 return true;
602
603 uint32_t len = getDenseInitializedLength();
604 const Value* elems = getDenseElements();
605 for (uint32_t i = 0; i < len; i++) {
606 if (!elems[i].isMagic(JS_ELEMENTS_HOLE) && !--minimalDenseCount)
607 return false;
608 }
609 return true;
610 }
611
612 /* static */ DenseElementResult
maybeDensifySparseElements(js::ExclusiveContext * cx,HandleNativeObject obj)613 NativeObject::maybeDensifySparseElements(js::ExclusiveContext* cx, HandleNativeObject obj)
614 {
615 /*
616 * Wait until after the object goes into dictionary mode, which must happen
617 * when sparsely packing any array with more than MIN_SPARSE_INDEX elements
618 * (see PropertyTree::MAX_HEIGHT).
619 */
620 if (!obj->inDictionaryMode())
621 return DenseElementResult::Incomplete;
622
623 /*
624 * Only measure the number of indexed properties every log(n) times when
625 * populating the object.
626 */
627 uint32_t slotSpan = obj->slotSpan();
628 if (slotSpan != RoundUpPow2(slotSpan))
629 return DenseElementResult::Incomplete;
630
631 /* Watch for conditions under which an object's elements cannot be dense. */
632 if (!obj->nonProxyIsExtensible() || obj->watched())
633 return DenseElementResult::Incomplete;
634
635 /*
636 * The indexes in the object need to be sufficiently dense before they can
637 * be converted to dense mode.
638 */
639 uint32_t numDenseElements = 0;
640 uint32_t newInitializedLength = 0;
641
642 RootedShape shape(cx, obj->lastProperty());
643 while (!shape->isEmptyShape()) {
644 uint32_t index;
645 if (IdIsIndex(shape->propid(), &index)) {
646 if (shape->attributes() == JSPROP_ENUMERATE &&
647 shape->hasDefaultGetter() &&
648 shape->hasDefaultSetter())
649 {
650 numDenseElements++;
651 newInitializedLength = Max(newInitializedLength, index + 1);
652 } else {
653 /*
654 * For simplicity, only densify the object if all indexed
655 * properties can be converted to dense elements.
656 */
657 return DenseElementResult::Incomplete;
658 }
659 }
660 shape = shape->previous();
661 }
662
663 if (numDenseElements * SPARSE_DENSITY_RATIO < newInitializedLength)
664 return DenseElementResult::Incomplete;
665
666 if (newInitializedLength > MAX_DENSE_ELEMENTS_COUNT)
667 return DenseElementResult::Incomplete;
668
669 /*
670 * This object meets all necessary restrictions, convert all indexed
671 * properties into dense elements.
672 */
673
674 if (!obj->maybeCopyElementsForWrite(cx))
675 return DenseElementResult::Failure;
676
677 if (newInitializedLength > obj->getDenseCapacity()) {
678 if (!obj->growElements(cx, newInitializedLength))
679 return DenseElementResult::Failure;
680 }
681
682 obj->ensureDenseInitializedLength(cx, newInitializedLength, 0);
683
684 RootedValue value(cx);
685
686 shape = obj->lastProperty();
687 while (!shape->isEmptyShape()) {
688 jsid id = shape->propid();
689 uint32_t index;
690 if (IdIsIndex(id, &index)) {
691 value = obj->getSlot(shape->slot());
692
693 /*
694 * When removing a property from a dictionary, the specified
695 * property will be removed from the dictionary list and the
696 * last property will then be changed due to reshaping the object.
697 * Compute the next shape in the traverse, watching for such
698 * removals from the list.
699 */
700 if (shape != obj->lastProperty()) {
701 shape = shape->previous();
702 if (!obj->removeProperty(cx, id))
703 return DenseElementResult::Failure;
704 } else {
705 if (!obj->removeProperty(cx, id))
706 return DenseElementResult::Failure;
707 shape = obj->lastProperty();
708 }
709
710 obj->setDenseElement(index, value);
711 } else {
712 shape = shape->previous();
713 }
714 }
715
716 /*
717 * All indexed properties on the object are now dense, clear the indexed
718 * flag so that we will not start using sparse indexes again if we need
719 * to grow the object.
720 */
721 if (!obj->clearFlag(cx, BaseShape::INDEXED))
722 return DenseElementResult::Failure;
723
724 return DenseElementResult::Success;
725 }
726
727 // Given a requested capacity (in elements) and (potentially) the length of an
728 // array for which elements are being allocated, compute an actual allocation
729 // amount (in elements). (Allocation amounts include space for an
730 // ObjectElements instance, so a return value of |N| implies
731 // |N - ObjectElements::VALUES_PER_HEADER| usable elements.)
732 //
733 // The requested/actual allocation distinction is meant to:
734 //
735 // * preserve amortized O(N) time to add N elements;
736 // * minimize the number of unused elements beyond an array's length, and
737 // * provide at least SLOT_CAPACITY_MIN elements no matter what (so adding
738 // the first several elements to small arrays only needs one allocation).
739 //
740 // Note: the structure and behavior of this method follow along with
741 // UnboxedArrayObject::chooseCapacityIndex. Changes to the allocation strategy
742 // in one should generally be matched by the other.
743 /* static */ bool
goodElementsAllocationAmount(ExclusiveContext * cx,uint32_t reqCapacity,uint32_t length,uint32_t * goodAmount)744 NativeObject::goodElementsAllocationAmount(ExclusiveContext* cx, uint32_t reqCapacity,
745 uint32_t length, uint32_t* goodAmount)
746 {
747 if (reqCapacity > MAX_DENSE_ELEMENTS_COUNT) {
748 ReportOutOfMemory(cx);
749 return false;
750 }
751
752 uint32_t reqAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER;
753
754 // Handle "small" requests primarily by doubling.
755 const uint32_t Mebi = 1 << 20;
756 if (reqAllocated < Mebi) {
757 uint32_t amount = mozilla::AssertedCast<uint32_t>(RoundUpPow2(reqAllocated));
758
759 // If |amount| would be 2/3 or more of the array's length, adjust
760 // it (up or down) to be equal to the array's length. This avoids
761 // allocating excess elements that aren't likely to be needed, either
762 // in this resizing or a subsequent one. The 2/3 factor is chosen so
763 // that exceptional resizings will at most triple the capacity, as
764 // opposed to the usual doubling.
765 uint32_t goodCapacity = amount - ObjectElements::VALUES_PER_HEADER;
766 if (length >= reqCapacity && goodCapacity > (length / 3) * 2)
767 amount = length + ObjectElements::VALUES_PER_HEADER;
768
769 if (amount < SLOT_CAPACITY_MIN)
770 amount = SLOT_CAPACITY_MIN;
771
772 *goodAmount = amount;
773
774 return true;
775 }
776
777 // The almost-doubling above wastes a lot of space for larger bucket sizes.
778 // For large amounts, switch to bucket sizes that obey this formula:
779 //
780 // count(n+1) = Math.ceil(count(n) * 1.125)
781 //
782 // where |count(n)| is the size of the nth bucket, measured in 2**20 slots.
783 // These bucket sizes still preserve amortized O(N) time to add N elements,
784 // just with a larger constant factor.
785 //
786 // The bucket size table below was generated with this JavaScript (and
787 // manual reformatting):
788 //
789 // for (let n = 1, i = 0; i < 34; i++) {
790 // print('0x' + (n * (1 << 20)).toString(16) + ', ');
791 // n = Math.ceil(n * 1.125);
792 // }
793 static const uint32_t BigBuckets[] = {
794 0x100000, 0x200000, 0x300000, 0x400000, 0x500000, 0x600000, 0x700000,
795 0x800000, 0x900000, 0xb00000, 0xd00000, 0xf00000, 0x1100000, 0x1400000,
796 0x1700000, 0x1a00000, 0x1e00000, 0x2200000, 0x2700000, 0x2c00000,
797 0x3200000, 0x3900000, 0x4100000, 0x4a00000, 0x5400000, 0x5f00000,
798 0x6b00000, 0x7900000, 0x8900000, 0x9b00000, 0xaf00000, 0xc500000,
799 0xde00000, 0xfa00000
800 };
801 MOZ_ASSERT(BigBuckets[ArrayLength(BigBuckets) - 1] <= MAX_DENSE_ELEMENTS_ALLOCATION);
802
803 // Pick the first bucket that'll fit |reqAllocated|.
804 for (uint32_t b : BigBuckets) {
805 if (b >= reqAllocated) {
806 *goodAmount = b;
807 return true;
808 }
809 }
810
811 // Otherwise, return the maximum bucket size.
812 *goodAmount = MAX_DENSE_ELEMENTS_ALLOCATION;
813 return true;
814 }
815
816 bool
growElements(ExclusiveContext * cx,uint32_t reqCapacity)817 NativeObject::growElements(ExclusiveContext* cx, uint32_t reqCapacity)
818 {
819 MOZ_ASSERT(nonProxyIsExtensible());
820 MOZ_ASSERT(canHaveNonEmptyElements());
821 MOZ_ASSERT(!denseElementsAreFrozen());
822 if (denseElementsAreCopyOnWrite())
823 MOZ_CRASH();
824
825 uint32_t oldCapacity = getDenseCapacity();
826 MOZ_ASSERT(oldCapacity < reqCapacity);
827
828 uint32_t newAllocated = 0;
829 if (is<ArrayObject>() && !as<ArrayObject>().lengthIsWritable()) {
830 MOZ_ASSERT(reqCapacity <= as<ArrayObject>().length());
831 MOZ_ASSERT(reqCapacity <= MAX_DENSE_ELEMENTS_COUNT);
832 // Preserve the |capacity <= length| invariant for arrays with
833 // non-writable length. See also js::ArraySetLength which initially
834 // enforces this requirement.
835 newAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER;
836 } else {
837 if (!goodElementsAllocationAmount(cx, reqCapacity, getElementsHeader()->length, &newAllocated))
838 return false;
839 }
840
841 uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
842 MOZ_ASSERT(newCapacity > oldCapacity && newCapacity >= reqCapacity);
843
844 // If newCapacity exceeds MAX_DENSE_ELEMENTS_COUNT, the array should become
845 // sparse.
846 MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
847
848 uint32_t initlen = getDenseInitializedLength();
849
850 HeapSlot* oldHeaderSlots = reinterpret_cast<HeapSlot*>(getElementsHeader());
851 HeapSlot* newHeaderSlots;
852 if (hasDynamicElements()) {
853 MOZ_ASSERT(oldCapacity <= MAX_DENSE_ELEMENTS_COUNT);
854 uint32_t oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER;
855
856 newHeaderSlots = ReallocateObjectBuffer<HeapSlot>(cx, this, oldHeaderSlots, oldAllocated, newAllocated);
857 if (!newHeaderSlots)
858 return false; // Leave elements at its old size.
859 } else {
860 newHeaderSlots = AllocateObjectBuffer<HeapSlot>(cx, this, newAllocated);
861 if (!newHeaderSlots)
862 return false; // Leave elements at its old size.
863 PodCopy(newHeaderSlots, oldHeaderSlots, ObjectElements::VALUES_PER_HEADER + initlen);
864 }
865
866 ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
867 newheader->capacity = newCapacity;
868 elements_ = newheader->elements();
869
870 Debug_SetSlotRangeToCrashOnTouch(elements_ + initlen, newCapacity - initlen);
871
872 return true;
873 }
874
875 void
shrinkElements(ExclusiveContext * cx,uint32_t reqCapacity)876 NativeObject::shrinkElements(ExclusiveContext* cx, uint32_t reqCapacity)
877 {
878 uint32_t oldCapacity = getDenseCapacity();
879 MOZ_ASSERT(reqCapacity < oldCapacity);
880
881 MOZ_ASSERT(canHaveNonEmptyElements());
882 if (denseElementsAreCopyOnWrite())
883 MOZ_CRASH();
884
885 if (!hasDynamicElements())
886 return;
887
888 uint32_t newAllocated = 0;
889 MOZ_ALWAYS_TRUE(goodElementsAllocationAmount(cx, reqCapacity, 0, &newAllocated));
890 MOZ_ASSERT(oldCapacity <= MAX_DENSE_ELEMENTS_COUNT);
891 uint32_t oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER;
892 if (newAllocated == oldAllocated)
893 return; // Leave elements at its old size.
894
895 MOZ_ASSERT(newAllocated > ObjectElements::VALUES_PER_HEADER);
896 uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
897 MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
898
899 HeapSlot* oldHeaderSlots = reinterpret_cast<HeapSlot*>(getElementsHeader());
900 HeapSlot* newHeaderSlots = ReallocateObjectBuffer<HeapSlot>(cx, this, oldHeaderSlots,
901 oldAllocated, newAllocated);
902 if (!newHeaderSlots) {
903 cx->recoverFromOutOfMemory();
904 return; // Leave elements at its old size.
905 }
906
907 ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
908 newheader->capacity = newCapacity;
909 elements_ = newheader->elements();
910 }
911
912 /* static */ bool
CopyElementsForWrite(ExclusiveContext * cx,NativeObject * obj)913 NativeObject::CopyElementsForWrite(ExclusiveContext* cx, NativeObject* obj)
914 {
915 MOZ_ASSERT(obj->denseElementsAreCopyOnWrite());
916 MOZ_ASSERT(!obj->denseElementsAreFrozen());
917
918 // The original owner of a COW elements array should never be modified.
919 MOZ_ASSERT(obj->getElementsHeader()->ownerObject() != obj);
920
921 uint32_t initlen = obj->getDenseInitializedLength();
922 uint32_t newAllocated = 0;
923 if (!goodElementsAllocationAmount(cx, initlen, 0, &newAllocated))
924 return false;
925
926 uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
927
928 // COPY_ON_WRITE flags is set only if obj is a dense array.
929 MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
930
931 JSObject::writeBarrierPre(obj->getElementsHeader()->ownerObject());
932
933 HeapSlot* newHeaderSlots = AllocateObjectBuffer<HeapSlot>(cx, obj, newAllocated);
934 if (!newHeaderSlots)
935 return false;
936 ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
937 js_memcpy(newheader, obj->getElementsHeader(),
938 (ObjectElements::VALUES_PER_HEADER + initlen) * sizeof(Value));
939
940 newheader->capacity = newCapacity;
941 newheader->clearCopyOnWrite();
942 obj->elements_ = newheader->elements();
943
944 Debug_SetSlotRangeToCrashOnTouch(obj->elements_ + initlen, newCapacity - initlen);
945
946 return true;
947 }
948
949 /* static */ bool
allocSlot(ExclusiveContext * cx,HandleNativeObject obj,uint32_t * slotp)950 NativeObject::allocSlot(ExclusiveContext* cx, HandleNativeObject obj, uint32_t* slotp)
951 {
952 uint32_t slot = obj->slotSpan();
953 MOZ_ASSERT(slot >= JSSLOT_FREE(obj->getClass()));
954
955 // If this object is in dictionary mode, try to pull a free slot from the
956 // shape table's slot-number free list. Shapes without a ShapeTable have an
957 // empty free list, because we only purge ShapeTables with an empty free
958 // list.
959 if (obj->inDictionaryMode()) {
960 AutoCheckCannotGC nogc;
961 if (ShapeTable* table = obj->lastProperty()->maybeTable(nogc)) {
962 uint32_t last = table->freeList();
963 if (last != SHAPE_INVALID_SLOT) {
964 #ifdef DEBUG
965 MOZ_ASSERT(last < slot);
966 uint32_t next = obj->getSlot(last).toPrivateUint32();
967 MOZ_ASSERT_IF(next != SHAPE_INVALID_SLOT, next < slot);
968 #endif
969
970 *slotp = last;
971
972 const Value& vref = obj->getSlot(last);
973 table->setFreeList(vref.toPrivateUint32());
974 obj->setSlot(last, UndefinedValue());
975 return true;
976 }
977 }
978 }
979
980 if (slot >= SHAPE_MAXIMUM_SLOT) {
981 ReportOutOfMemory(cx);
982 return false;
983 }
984
985 *slotp = slot;
986
987 if (obj->inDictionaryMode() && !obj->setSlotSpan(cx, slot + 1))
988 return false;
989
990 return true;
991 }
992
993 void
freeSlot(ExclusiveContext * cx,uint32_t slot)994 NativeObject::freeSlot(ExclusiveContext* cx, uint32_t slot)
995 {
996 MOZ_ASSERT(slot < slotSpan());
997
998 if (inDictionaryMode()) {
999 // Ensure we have a ShapeTable as it stores the object's free list (the
1000 // list of available slots in dictionary objects).
1001 AutoCheckCannotGC nogc;
1002 if (ShapeTable* table = lastProperty()->ensureTableForDictionary(cx, nogc)) {
1003 uint32_t last = table->freeList();
1004
1005 // Can't afford to check the whole free list, but let's check the head.
1006 MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan() && last != slot);
1007
1008 // Place all freed slots other than reserved slots (bug 595230) on the
1009 // dictionary's free list.
1010 if (JSSLOT_FREE(getClass()) <= slot) {
1011 MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan());
1012 setSlot(slot, PrivateUint32Value(last));
1013 table->setFreeList(slot);
1014 return;
1015 }
1016 } else {
1017 // OOM while creating the ShapeTable holding the free list. We can
1018 // recover from it - it just means we won't be able to reuse this
1019 // slot later.
1020 cx->recoverFromOutOfMemory();
1021 }
1022 }
1023 setSlot(slot, UndefinedValue());
1024 }
1025
1026 Shape*
addDataProperty(ExclusiveContext * cx,jsid idArg,uint32_t slot,unsigned attrs)1027 NativeObject::addDataProperty(ExclusiveContext* cx, jsid idArg, uint32_t slot, unsigned attrs)
1028 {
1029 MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
1030 RootedNativeObject self(cx, this);
1031 RootedId id(cx, idArg);
1032 return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0);
1033 }
1034
1035 Shape*
addDataProperty(ExclusiveContext * cx,HandlePropertyName name,uint32_t slot,unsigned attrs)1036 NativeObject::addDataProperty(ExclusiveContext* cx, HandlePropertyName name,
1037 uint32_t slot, unsigned attrs)
1038 {
1039 MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
1040 RootedNativeObject self(cx, this);
1041 RootedId id(cx, NameToId(name));
1042 return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0);
1043 }
1044
1045 template <AllowGC allowGC>
1046 bool
NativeLookupOwnProperty(ExclusiveContext * cx,typename MaybeRooted<NativeObject *,allowGC>::HandleType obj,typename MaybeRooted<jsid,allowGC>::HandleType id,typename MaybeRooted<Shape *,allowGC>::MutableHandleType propp)1047 js::NativeLookupOwnProperty(ExclusiveContext* cx,
1048 typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
1049 typename MaybeRooted<jsid, allowGC>::HandleType id,
1050 typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp)
1051 {
1052 bool done;
1053 return LookupOwnPropertyInline<allowGC>(cx, obj, id, propp, &done);
1054 }
1055
1056 template bool
1057 js::NativeLookupOwnProperty<CanGC>(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
1058 MutableHandleShape propp);
1059
1060 template bool
1061 js::NativeLookupOwnProperty<NoGC>(ExclusiveContext* cx, NativeObject* const& obj, const jsid& id,
1062 FakeMutableHandle<Shape*> propp);
1063
1064 /*** [[DefineOwnProperty]] ***********************************************************************/
1065
1066 static inline bool
CallAddPropertyHook(ExclusiveContext * cx,HandleNativeObject obj,HandleShape shape,HandleValue value)1067 CallAddPropertyHook(ExclusiveContext* cx, HandleNativeObject obj, HandleShape shape,
1068 HandleValue value)
1069 {
1070 if (JSAddPropertyOp addProperty = obj->getClass()->getAddProperty()) {
1071 if (!cx->shouldBeJSContext())
1072 return false;
1073
1074 RootedId id(cx, shape->propid());
1075 if (!CallJSAddPropertyOp(cx->asJSContext(), addProperty, obj, id, value)) {
1076 obj->removeProperty(cx, shape->propid());
1077 return false;
1078 }
1079 }
1080 return true;
1081 }
1082
1083 static inline bool
CallAddPropertyHookDense(ExclusiveContext * cx,HandleNativeObject obj,uint32_t index,HandleValue value)1084 CallAddPropertyHookDense(ExclusiveContext* cx, HandleNativeObject obj, uint32_t index,
1085 HandleValue value)
1086 {
1087 // Inline addProperty for array objects.
1088 if (obj->is<ArrayObject>()) {
1089 ArrayObject* arr = &obj->as<ArrayObject>();
1090 uint32_t length = arr->length();
1091 if (index >= length)
1092 arr->setLength(cx, index + 1);
1093 return true;
1094 }
1095
1096 if (JSAddPropertyOp addProperty = obj->getClass()->getAddProperty()) {
1097 if (!cx->shouldBeJSContext())
1098 return false;
1099
1100 if (!obj->maybeCopyElementsForWrite(cx))
1101 return false;
1102
1103 RootedId id(cx, INT_TO_JSID(index));
1104 if (!CallJSAddPropertyOp(cx->asJSContext(), addProperty, obj, id, value)) {
1105 obj->setDenseElementHole(cx, index);
1106 return false;
1107 }
1108 }
1109 return true;
1110 }
1111
1112 static bool
UpdateShapeTypeAndValue(ExclusiveContext * cx,HandleNativeObject obj,HandleShape shape,const Value & value)1113 UpdateShapeTypeAndValue(ExclusiveContext* cx, HandleNativeObject obj, HandleShape shape, const Value& value)
1114 {
1115 jsid id = shape->propid();
1116 if (shape->hasSlot()) {
1117 obj->setSlotWithType(cx, shape, value, /* overwriting = */ false);
1118
1119 // Per the acquired properties analysis, when the shape of a partially
1120 // initialized object is changed to its fully initialized shape, its
1121 // group can be updated as well.
1122 if (TypeNewScript* newScript = obj->groupRaw()->newScript()) {
1123 if (newScript->initializedShape() == shape)
1124 obj->setGroup(newScript->initializedGroup());
1125 }
1126 }
1127 if (!shape->hasSlot() || !shape->hasDefaultGetter() || !shape->hasDefaultSetter())
1128 MarkTypePropertyNonData(cx, obj, id);
1129 if (!shape->writable())
1130 MarkTypePropertyNonWritable(cx, obj, id);
1131 return true;
1132 }
1133
1134 static bool
PurgeProtoChain(ExclusiveContext * cx,JSObject * objArg,HandleId id)1135 PurgeProtoChain(ExclusiveContext* cx, JSObject* objArg, HandleId id)
1136 {
1137 /* Root locally so we can re-assign. */
1138 RootedObject obj(cx, objArg);
1139
1140 RootedShape shape(cx);
1141 while (obj) {
1142 /* Lookups will not be cached through non-native protos. */
1143 if (!obj->isNative())
1144 break;
1145
1146 shape = obj->as<NativeObject>().lookup(cx, id);
1147 if (shape)
1148 return obj->as<NativeObject>().shadowingShapeChange(cx, *shape);
1149
1150 obj = obj->staticPrototype();
1151 }
1152
1153 return true;
1154 }
1155
1156 static bool
PurgeEnvironmentChainHelper(ExclusiveContext * cx,HandleObject objArg,HandleId id)1157 PurgeEnvironmentChainHelper(ExclusiveContext* cx, HandleObject objArg, HandleId id)
1158 {
1159 /* Re-root locally so we can re-assign. */
1160 RootedObject obj(cx, objArg);
1161
1162 MOZ_ASSERT(obj->isNative());
1163 MOZ_ASSERT(obj->isDelegate());
1164
1165 /* Lookups on integer ids cannot be cached through prototypes. */
1166 if (JSID_IS_INT(id))
1167 return true;
1168
1169 if (!PurgeProtoChain(cx, obj->staticPrototype(), id))
1170 return false;
1171
1172 /*
1173 * We must purge the environment chain only for Call objects as they are
1174 * the only kind of cacheable non-global object that can gain properties
1175 * after outer properties with the same names have been cached or
1176 * traced. Call objects may gain such properties via eval introducing new
1177 * vars; see bug 490364.
1178 */
1179 if (obj->is<CallObject>()) {
1180 while ((obj = obj->enclosingEnvironment()) != nullptr) {
1181 if (!PurgeProtoChain(cx, obj, id))
1182 return false;
1183 }
1184 }
1185
1186 return true;
1187 }
1188
1189 /*
1190 * PurgeEnvironmentChain does nothing if obj is not itself a prototype or
1191 * parent environment, else it reshapes the scope and prototype chains it
1192 * links. It calls PurgeEnvironmentChainHelper, which asserts that obj is
1193 * flagged as a delegate (i.e., obj has ever been on a prototype or parent
1194 * chain).
1195 */
1196 static inline bool
PurgeEnvironmentChain(ExclusiveContext * cx,HandleObject obj,HandleId id)1197 PurgeEnvironmentChain(ExclusiveContext* cx, HandleObject obj, HandleId id)
1198 {
1199 if (obj->isDelegate() && obj->isNative())
1200 return PurgeEnvironmentChainHelper(cx, obj, id);
1201 return true;
1202 }
1203
1204 static bool
AddOrChangeProperty(ExclusiveContext * cx,HandleNativeObject obj,HandleId id,Handle<PropertyDescriptor> desc)1205 AddOrChangeProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
1206 Handle<PropertyDescriptor> desc)
1207 {
1208 desc.assertComplete();
1209
1210 if (!PurgeEnvironmentChain(cx, obj, id))
1211 return false;
1212
1213 // Use dense storage for new indexed properties where possible.
1214 if (JSID_IS_INT(id) &&
1215 !desc.getter() &&
1216 !desc.setter() &&
1217 desc.attributes() == JSPROP_ENUMERATE &&
1218 (!obj->isIndexed() || !obj->containsPure(id)) &&
1219 !obj->is<TypedArrayObject>())
1220 {
1221 uint32_t index = JSID_TO_INT(id);
1222 DenseElementResult edResult = obj->ensureDenseElements(cx, index, 1);
1223 if (edResult == DenseElementResult::Failure)
1224 return false;
1225 if (edResult == DenseElementResult::Success) {
1226 obj->setDenseElementWithType(cx, index, desc.value());
1227 if (!CallAddPropertyHookDense(cx, obj, index, desc.value()))
1228 return false;
1229 return true;
1230 }
1231 }
1232
1233 RootedShape shape(cx, NativeObject::putProperty(cx, obj, id, desc.getter(), desc.setter(),
1234 SHAPE_INVALID_SLOT, desc.attributes(), 0));
1235 if (!shape)
1236 return false;
1237
1238 if (!UpdateShapeTypeAndValue(cx, obj, shape, desc.value()))
1239 return false;
1240
1241 // Clear any existing dense index after adding a sparse indexed property,
1242 // and investigate converting the object to dense indexes.
1243 if (JSID_IS_INT(id)) {
1244 if (!obj->maybeCopyElementsForWrite(cx))
1245 return false;
1246
1247 uint32_t index = JSID_TO_INT(id);
1248 NativeObject::removeDenseElementForSparseIndex(cx, obj, index);
1249 DenseElementResult edResult =
1250 NativeObject::maybeDensifySparseElements(cx, obj);
1251 if (edResult == DenseElementResult::Failure)
1252 return false;
1253 if (edResult == DenseElementResult::Success) {
1254 MOZ_ASSERT(!desc.setter());
1255 return CallAddPropertyHookDense(cx, obj, index, desc.value());
1256 }
1257 }
1258
1259 return CallAddPropertyHook(cx, obj, shape, desc.value());
1260 }
1261
IsConfigurable(unsigned attrs)1262 static bool IsConfigurable(unsigned attrs) { return (attrs & JSPROP_PERMANENT) == 0; }
IsEnumerable(unsigned attrs)1263 static bool IsEnumerable(unsigned attrs) { return (attrs & JSPROP_ENUMERATE) != 0; }
IsWritable(unsigned attrs)1264 static bool IsWritable(unsigned attrs) { return (attrs & JSPROP_READONLY) == 0; }
1265
IsAccessorDescriptor(unsigned attrs)1266 static bool IsAccessorDescriptor(unsigned attrs) {
1267 return (attrs & (JSPROP_GETTER | JSPROP_SETTER)) != 0;
1268 }
1269
IsDataDescriptor(unsigned attrs)1270 static bool IsDataDescriptor(unsigned attrs) {
1271 MOZ_ASSERT((attrs & (JSPROP_IGNORE_VALUE | JSPROP_IGNORE_READONLY)) == 0);
1272 return !IsAccessorDescriptor(attrs);
1273 }
1274
1275 template <AllowGC allowGC>
1276 static MOZ_ALWAYS_INLINE bool
1277 GetExistingProperty(JSContext* cx,
1278 typename MaybeRooted<Value, allowGC>::HandleType receiver,
1279 typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
1280 typename MaybeRooted<Shape*, allowGC>::HandleType shape,
1281 typename MaybeRooted<Value, allowGC>::MutableHandleType vp);
1282
1283 static bool
GetExistingPropertyValue(ExclusiveContext * cx,HandleNativeObject obj,HandleId id,HandleShape shape,MutableHandleValue vp)1284 GetExistingPropertyValue(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
1285 HandleShape shape, MutableHandleValue vp)
1286 {
1287 if (IsImplicitDenseOrTypedArrayElement(shape)) {
1288 vp.set(obj->getDenseOrTypedArrayElement(JSID_TO_INT(id)));
1289 return true;
1290 }
1291 if (!cx->shouldBeJSContext())
1292 return false;
1293
1294 MOZ_ASSERT(shape->propid() == id);
1295 MOZ_ASSERT(obj->contains(cx, shape));
1296
1297 RootedValue receiver(cx, ObjectValue(*obj));
1298 return GetExistingProperty<CanGC>(cx->asJSContext(), receiver, obj, shape, vp);
1299 }
1300
1301 /*
1302 * If ES6 draft rev 37 9.1.6.3 ValidateAndApplyPropertyDescriptor step 4 would
1303 * return early, because desc is redundant with an existing own property obj[id],
1304 * then set *redundant = true and return true.
1305 */
1306 static bool
DefinePropertyIsRedundant(ExclusiveContext * cx,HandleNativeObject obj,HandleId id,HandleShape shape,unsigned shapeAttrs,Handle<PropertyDescriptor> desc,bool * redundant)1307 DefinePropertyIsRedundant(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
1308 HandleShape shape, unsigned shapeAttrs,
1309 Handle<PropertyDescriptor> desc, bool *redundant)
1310 {
1311 *redundant = false;
1312
1313 if (desc.hasConfigurable() && desc.configurable() != ((shapeAttrs & JSPROP_PERMANENT) == 0))
1314 return true;
1315 if (desc.hasEnumerable() && desc.enumerable() != ((shapeAttrs & JSPROP_ENUMERATE) != 0))
1316 return true;
1317 if (desc.isDataDescriptor()) {
1318 if ((shapeAttrs & (JSPROP_GETTER | JSPROP_SETTER)) != 0)
1319 return true;
1320 if (desc.hasWritable() && desc.writable() != ((shapeAttrs & JSPROP_READONLY) == 0))
1321 return true;
1322 if (desc.hasValue()) {
1323 // Get the current value of the existing property.
1324 RootedValue currentValue(cx);
1325 if (!IsImplicitDenseOrTypedArrayElement(shape) &&
1326 shape->hasSlot() &&
1327 shape->hasDefaultGetter())
1328 {
1329 // Inline GetExistingPropertyValue in order to omit a type
1330 // correctness assertion that's too strict for this particular
1331 // call site. For details, see bug 1125624 comments 13-16.
1332 currentValue.set(obj->getSlot(shape->slot()));
1333 } else {
1334 if (!GetExistingPropertyValue(cx, obj, id, shape, ¤tValue))
1335 return false;
1336 }
1337
1338 // The specification calls for SameValue here, but it seems to be a
1339 // bug. See <https://bugs.ecmascript.org/show_bug.cgi?id=3508>.
1340 if (desc.value() != currentValue)
1341 return true;
1342 }
1343
1344 GetterOp existingGetterOp =
1345 IsImplicitDenseOrTypedArrayElement(shape) ? nullptr : shape->getter();
1346 if (desc.getter() != existingGetterOp)
1347 return true;
1348
1349 SetterOp existingSetterOp =
1350 IsImplicitDenseOrTypedArrayElement(shape) ? nullptr : shape->setter();
1351 if (desc.setter() != existingSetterOp)
1352 return true;
1353 } else {
1354 if (desc.hasGetterObject()) {
1355 if (!(shapeAttrs & JSPROP_GETTER) || desc.getterObject() != shape->getterObject())
1356 return true;
1357 }
1358 if (desc.hasSetterObject()) {
1359 if (!(shapeAttrs & JSPROP_SETTER) || desc.setterObject() != shape->setterObject())
1360 return true;
1361 }
1362 }
1363
1364 *redundant = true;
1365 return true;
1366 }
1367
1368 bool
NativeDefineProperty(ExclusiveContext * cx,HandleNativeObject obj,HandleId id,Handle<PropertyDescriptor> desc_,ObjectOpResult & result)1369 js::NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
1370 Handle<PropertyDescriptor> desc_,
1371 ObjectOpResult& result)
1372 {
1373 desc_.assertValid();
1374
1375 // Section numbers and step numbers below refer to ES6 draft rev 36
1376 // (17 March 2015).
1377 //
1378 // This function aims to implement 9.1.6 [[DefineOwnProperty]] as well as
1379 // the [[DefineOwnProperty]] methods described in 9.4.2.1 (arrays), 9.4.4.2
1380 // (arguments), and 9.4.5.3 (typed array views).
1381
1382 // Dispense with custom behavior of exotic native objects first.
1383 if (obj->is<ArrayObject>()) {
1384 // 9.4.2.1 step 2. Redefining an array's length is very special.
1385 Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
1386 if (id == NameToId(cx->names().length)) {
1387 if (!cx->shouldBeJSContext())
1388 return false;
1389 return ArraySetLength(cx->asJSContext(), arr, id, desc_.attributes(), desc_.value(),
1390 result);
1391 }
1392
1393 // 9.4.2.1 step 3. Don't extend a fixed-length array.
1394 uint32_t index;
1395 if (IdIsIndex(id, &index)) {
1396 if (WouldDefinePastNonwritableLength(obj, index))
1397 return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
1398 }
1399 } else if (obj->is<TypedArrayObject>()) {
1400 // 9.4.5.3 step 3. Indexed properties of typed arrays are special.
1401 uint64_t index;
1402 if (IsTypedArrayIndex(id, &index)) {
1403 if (!cx->shouldBeJSContext())
1404 return false;
1405 return DefineTypedArrayElement(cx->asJSContext(), obj, index, desc_, result);
1406 }
1407 } else if (obj->is<ArgumentsObject>()) {
1408 if (id == NameToId(cx->names().length)) {
1409 // Either we are resolving the .length property on this object, or
1410 // redefining it. In the latter case only, we must set a bit. To
1411 // distinguish the two cases, we note that when resolving, the
1412 // property won't already exist; whereas the first time it is
1413 // redefined, it will.
1414 if ((desc_.attributes() & JSPROP_RESOLVING) == 0)
1415 obj->as<ArgumentsObject>().markLengthOverridden();
1416 } else if (JSID_IS_SYMBOL(id) && JSID_TO_SYMBOL(id) == cx->wellKnownSymbols().iterator) {
1417 // Do same thing as .length for [@@iterator].
1418 if ((desc_.attributes() & JSPROP_RESOLVING) == 0)
1419 obj->as<ArgumentsObject>().markIteratorOverridden();
1420 } else if (JSID_IS_INT(id)) {
1421 if ((desc_.attributes() & JSPROP_RESOLVING) == 0)
1422 obj->as<ArgumentsObject>().markElementOverridden();
1423 }
1424 }
1425
1426 // 9.1.6.1 OrdinaryDefineOwnProperty steps 1-2.
1427 RootedShape shape(cx);
1428 if (desc_.attributes() & JSPROP_RESOLVING) {
1429 // We are being called from a resolve or enumerate hook to reify a
1430 // lazily-resolved property. To avoid reentering the resolve hook and
1431 // recursing forever, skip the resolve hook when doing this lookup.
1432 NativeLookupOwnPropertyNoResolve(cx, obj, id, &shape);
1433 } else {
1434 if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &shape))
1435 return false;
1436 }
1437
1438 // From this point, the step numbers refer to
1439 // 9.1.6.3, ValidateAndApplyPropertyDescriptor.
1440 // Step 1 is a redundant assertion.
1441
1442 // Filling in desc: Here we make a copy of the desc_ argument. We will turn
1443 // it into a complete descriptor before updating obj. The spec algorithm
1444 // does not explicitly do this, but the end result is the same. Search for
1445 // "fill in" below for places where the filling-in actually occurs.
1446 Rooted<PropertyDescriptor> desc(cx, desc_);
1447
1448 // Step 2.
1449 if (!shape) {
1450 if (!obj->nonProxyIsExtensible())
1451 return result.fail(JSMSG_CANT_DEFINE_PROP_OBJECT_NOT_EXTENSIBLE);
1452
1453 // Fill in missing desc fields with defaults.
1454 CompletePropertyDescriptor(&desc);
1455
1456 if (!AddOrChangeProperty(cx, obj, id, desc))
1457 return false;
1458 return result.succeed();
1459 }
1460
1461 MOZ_ASSERT(shape);
1462
1463 // Steps 3-4. (Step 3 is a special case of step 4.) We use shapeAttrs as a
1464 // stand-in for shape in many places below, since shape might not be a
1465 // pointer to a real Shape (see IsImplicitDenseOrTypedArrayElement).
1466 unsigned shapeAttrs = GetShapeAttributes(obj, shape);
1467 bool redundant;
1468 if (!DefinePropertyIsRedundant(cx, obj, id, shape, shapeAttrs, desc, &redundant))
1469 return false;
1470 if (redundant) {
1471 // In cases involving JSOP_NEWOBJECT and JSOP_INITPROP, obj can have a
1472 // type for this property that doesn't match the value in the slot.
1473 // Update the type here, even though this DefineProperty call is
1474 // otherwise a no-op. (See bug 1125624 comment 13.)
1475 if (!IsImplicitDenseOrTypedArrayElement(shape) && desc.hasValue()) {
1476 if (!UpdateShapeTypeAndValue(cx, obj, shape, desc.value()))
1477 return false;
1478 }
1479 return result.succeed();
1480 }
1481
1482 // Non-standard hack: Allow redefining non-configurable properties if
1483 // JSPROP_REDEFINE_NONCONFIGURABLE is set _and_ the object is a non-DOM
1484 // global. The idea is that a DOM object can never have such a thing on
1485 // its proto chain directly on the web, so we should be OK optimizing
1486 // access to accessors found on such an object. Bug 1105518 contemplates
1487 // removing this hack.
1488 bool skipRedefineChecks = (desc.attributes() & JSPROP_REDEFINE_NONCONFIGURABLE) &&
1489 obj->is<GlobalObject>() &&
1490 !obj->getClass()->isDOMClass();
1491
1492 // Step 5.
1493 if (!IsConfigurable(shapeAttrs) && !skipRedefineChecks) {
1494 if (desc.hasConfigurable() && desc.configurable())
1495 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1496 if (desc.hasEnumerable() && desc.enumerable() != IsEnumerable(shapeAttrs))
1497 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1498 }
1499
1500 // Fill in desc.[[Configurable]] and desc.[[Enumerable]] if missing.
1501 if (!desc.hasConfigurable())
1502 desc.setConfigurable(IsConfigurable(shapeAttrs));
1503 if (!desc.hasEnumerable())
1504 desc.setEnumerable(IsEnumerable(shapeAttrs));
1505
1506 // Steps 6-9.
1507 if (desc.isGenericDescriptor()) {
1508 // Step 6. No further validation is required.
1509
1510 // Fill in desc. A generic descriptor has none of these fields, so copy
1511 // everything from shape.
1512 MOZ_ASSERT(!desc.hasValue());
1513 MOZ_ASSERT(!desc.hasWritable());
1514 MOZ_ASSERT(!desc.hasGetterObject());
1515 MOZ_ASSERT(!desc.hasSetterObject());
1516 if (IsDataDescriptor(shapeAttrs)) {
1517 RootedValue currentValue(cx);
1518 if (!GetExistingPropertyValue(cx, obj, id, shape, ¤tValue))
1519 return false;
1520 desc.setValue(currentValue);
1521 desc.setWritable(IsWritable(shapeAttrs));
1522 } else {
1523 desc.setGetterObject(shape->getterObject());
1524 desc.setSetterObject(shape->setterObject());
1525 }
1526 } else if (desc.isDataDescriptor() != IsDataDescriptor(shapeAttrs)) {
1527 // Step 7.
1528 if (!IsConfigurable(shapeAttrs) && !skipRedefineChecks)
1529 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1530
1531 if (IsImplicitDenseOrTypedArrayElement(shape)) {
1532 MOZ_ASSERT(!obj->is<TypedArrayObject>());
1533 if (!NativeObject::sparsifyDenseElement(cx, obj, JSID_TO_INT(id)))
1534 return false;
1535 shape = obj->lookup(cx, id);
1536 }
1537
1538 // Fill in desc fields with default values (steps 7.b.i and 7.c.i).
1539 CompletePropertyDescriptor(&desc);
1540 } else if (desc.isDataDescriptor()) {
1541 // Step 8.
1542 bool frozen = !IsConfigurable(shapeAttrs) && !IsWritable(shapeAttrs);
1543 if (frozen && desc.hasWritable() && desc.writable() && !skipRedefineChecks)
1544 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1545
1546 if (frozen || !desc.hasValue()) {
1547 if (IsImplicitDenseOrTypedArrayElement(shape)) {
1548 MOZ_ASSERT(!obj->is<TypedArrayObject>());
1549 if (!NativeObject::sparsifyDenseElement(cx, obj, JSID_TO_INT(id)))
1550 return false;
1551 shape = obj->lookup(cx, id);
1552 }
1553
1554 RootedValue currentValue(cx);
1555 if (!GetExistingPropertyValue(cx, obj, id, shape, ¤tValue))
1556 return false;
1557
1558 if (!desc.hasValue()) {
1559 // Fill in desc.[[Value]].
1560 desc.setValue(currentValue);
1561 } else {
1562 // Step 8.a.ii.1.
1563 bool same;
1564 if (!cx->shouldBeJSContext())
1565 return false;
1566 if (!SameValue(cx->asJSContext(), desc.value(), currentValue, &same))
1567 return false;
1568 if (!same && !skipRedefineChecks)
1569 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1570 }
1571 }
1572
1573 if (!desc.hasWritable())
1574 desc.setWritable(IsWritable(shapeAttrs));
1575 } else {
1576 // Step 9.
1577 MOZ_ASSERT(shape->isAccessorDescriptor());
1578 MOZ_ASSERT(desc.isAccessorDescriptor());
1579
1580 // The spec says to use SameValue, but since the values in
1581 // question are objects, we can just compare pointers.
1582 if (desc.hasSetterObject()) {
1583 if (!IsConfigurable(shapeAttrs) &&
1584 desc.setterObject() != shape->setterObject() &&
1585 !skipRedefineChecks)
1586 {
1587 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1588 }
1589 } else {
1590 // Fill in desc.[[Set]] from shape.
1591 desc.setSetterObject(shape->setterObject());
1592 }
1593 if (desc.hasGetterObject()) {
1594 if (!IsConfigurable(shapeAttrs) &&
1595 desc.getterObject() != shape->getterObject() &&
1596 !skipRedefineChecks)
1597 {
1598 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1599 }
1600 } else {
1601 // Fill in desc.[[Get]] from shape.
1602 desc.setGetterObject(shape->getterObject());
1603 }
1604 }
1605
1606 // Step 10.
1607 if (!AddOrChangeProperty(cx, obj, id, desc))
1608 return false;
1609 return result.succeed();
1610 }
1611
1612 bool
NativeDefineProperty(ExclusiveContext * cx,HandleNativeObject obj,HandleId id,HandleValue value,GetterOp getter,SetterOp setter,unsigned attrs,ObjectOpResult & result)1613 js::NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
1614 HandleValue value, GetterOp getter, SetterOp setter, unsigned attrs,
1615 ObjectOpResult& result)
1616 {
1617 Rooted<PropertyDescriptor> desc(cx);
1618 desc.initFields(nullptr, value, attrs, getter, setter);
1619 return NativeDefineProperty(cx, obj, id, desc, result);
1620 }
1621
1622 bool
NativeDefineProperty(ExclusiveContext * cx,HandleNativeObject obj,PropertyName * name,HandleValue value,GetterOp getter,SetterOp setter,unsigned attrs,ObjectOpResult & result)1623 js::NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, PropertyName* name,
1624 HandleValue value, GetterOp getter, SetterOp setter, unsigned attrs,
1625 ObjectOpResult& result)
1626 {
1627 RootedId id(cx, NameToId(name));
1628 return NativeDefineProperty(cx, obj, id, value, getter, setter, attrs, result);
1629 }
1630
1631 bool
NativeDefineElement(ExclusiveContext * cx,HandleNativeObject obj,uint32_t index,HandleValue value,GetterOp getter,SetterOp setter,unsigned attrs,ObjectOpResult & result)1632 js::NativeDefineElement(ExclusiveContext* cx, HandleNativeObject obj, uint32_t index,
1633 HandleValue value, GetterOp getter, SetterOp setter, unsigned attrs,
1634 ObjectOpResult& result)
1635 {
1636 RootedId id(cx);
1637 if (index <= JSID_INT_MAX) {
1638 id = INT_TO_JSID(index);
1639 return NativeDefineProperty(cx, obj, id, value, getter, setter, attrs, result);
1640 }
1641
1642 AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter);
1643
1644 if (!IndexToId(cx, index, &id))
1645 return false;
1646
1647 return NativeDefineProperty(cx, obj, id, value, getter, setter, attrs, result);
1648 }
1649
1650 bool
NativeDefineProperty(ExclusiveContext * cx,HandleNativeObject obj,HandleId id,HandleValue value,JSGetterOp getter,JSSetterOp setter,unsigned attrs)1651 js::NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
1652 HandleValue value, JSGetterOp getter, JSSetterOp setter,
1653 unsigned attrs)
1654 {
1655 ObjectOpResult result;
1656 if (!NativeDefineProperty(cx, obj, id, value, getter, setter, attrs, result))
1657 return false;
1658 if (!result) {
1659 // Off-main-thread callers should not get here: they must call this
1660 // function only with known-valid arguments. Populating a new
1661 // PlainObject with configurable properties is fine.
1662 if (!cx->shouldBeJSContext())
1663 return false;
1664 result.reportError(cx->asJSContext(), obj, id);
1665 return false;
1666 }
1667 return true;
1668 }
1669
1670 bool
NativeDefineProperty(ExclusiveContext * cx,HandleNativeObject obj,PropertyName * name,HandleValue value,JSGetterOp getter,JSSetterOp setter,unsigned attrs)1671 js::NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, PropertyName* name,
1672 HandleValue value, JSGetterOp getter, JSSetterOp setter,
1673 unsigned attrs)
1674 {
1675 RootedId id(cx, NameToId(name));
1676 return NativeDefineProperty(cx, obj, id, value, getter, setter, attrs);
1677 }
1678
1679
1680 /*** [[HasProperty]] *****************************************************************************/
1681
1682 // ES6 draft rev31 9.1.7.1 OrdinaryHasProperty
1683 bool
NativeHasProperty(JSContext * cx,HandleNativeObject obj,HandleId id,bool * foundp)1684 js::NativeHasProperty(JSContext* cx, HandleNativeObject obj, HandleId id, bool* foundp)
1685 {
1686 RootedNativeObject pobj(cx, obj);
1687 RootedShape shape(cx);
1688
1689 // This loop isn't explicit in the spec algorithm. See the comment on step
1690 // 7.a. below.
1691 for (;;) {
1692 // Steps 2-3. ('done' is a SpiderMonkey-specific thing, used below.)
1693 bool done;
1694 if (!LookupOwnPropertyInline<CanGC>(cx, pobj, id, &shape, &done))
1695 return false;
1696
1697 // Step 4.
1698 if (shape) {
1699 *foundp = true;
1700 return true;
1701 }
1702
1703 // Step 5-6. The check for 'done' on this next line is tricky.
1704 // done can be true in exactly these unlikely-sounding cases:
1705 // - We're looking up an element, and pobj is a TypedArray that
1706 // doesn't have that many elements.
1707 // - We're being called from a resolve hook to assign to the property
1708 // being resolved.
1709 // What they all have in common is we do not want to keep walking
1710 // the prototype chain, and always claim that the property
1711 // doesn't exist.
1712 RootedObject proto(cx, done ? nullptr : pobj->staticPrototype());
1713
1714 // Step 8.
1715 if (!proto) {
1716 *foundp = false;
1717 return true;
1718 }
1719
1720 // Step 7.a. If the prototype is also native, this step is a
1721 // recursive tail call, and we don't need to go through all the
1722 // plumbing of HasProperty; the top of the loop is where
1723 // we're going to end up anyway. But if pobj is non-native,
1724 // that optimization would be incorrect.
1725 if (!proto->isNative())
1726 return HasProperty(cx, proto, id, foundp);
1727
1728 pobj = &proto->as<NativeObject>();
1729 }
1730 }
1731
1732
1733 /*** [[GetOwnPropertyDescriptor]] ****************************************************************/
1734
1735 bool
NativeGetOwnPropertyDescriptor(JSContext * cx,HandleNativeObject obj,HandleId id,MutableHandle<PropertyDescriptor> desc)1736 js::NativeGetOwnPropertyDescriptor(JSContext* cx, HandleNativeObject obj, HandleId id,
1737 MutableHandle<PropertyDescriptor> desc)
1738 {
1739 RootedShape shape(cx);
1740 if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &shape))
1741 return false;
1742 if (!shape) {
1743 desc.object().set(nullptr);
1744 return true;
1745 }
1746
1747 desc.setAttributes(GetShapeAttributes(obj, shape));
1748 if (desc.isAccessorDescriptor()) {
1749 MOZ_ASSERT(desc.isShared());
1750
1751 // The result of GetOwnPropertyDescriptor() must be either undefined or
1752 // a complete property descriptor (per ES6 draft rev 32 (2015 Feb 2)
1753 // 6.1.7.3, Invariants of the Essential Internal Methods).
1754 //
1755 // It is an unfortunate fact that in SM, properties can exist that have
1756 // JSPROP_GETTER or JSPROP_SETTER but not both. In these cases, rather
1757 // than return true with desc incomplete, we fill out the missing
1758 // getter or setter with a null, following CompletePropertyDescriptor.
1759 if (desc.hasGetterObject()) {
1760 desc.setGetterObject(shape->getterObject());
1761 } else {
1762 desc.setGetterObject(nullptr);
1763 desc.attributesRef() |= JSPROP_GETTER;
1764 }
1765 if (desc.hasSetterObject()) {
1766 desc.setSetterObject(shape->setterObject());
1767 } else {
1768 desc.setSetterObject(nullptr);
1769 desc.attributesRef() |= JSPROP_SETTER;
1770 }
1771
1772 desc.value().setUndefined();
1773 } else {
1774 // This is either a straight-up data property or (rarely) a
1775 // property with a JSGetterOp/JSSetterOp. The latter must be
1776 // reported to the caller as a plain data property, so clear
1777 // desc.getter/setter, and mask away the SHARED bit.
1778 desc.setGetter(nullptr);
1779 desc.setSetter(nullptr);
1780 desc.attributesRef() &= ~JSPROP_SHARED;
1781
1782 if (IsImplicitDenseOrTypedArrayElement(shape)) {
1783 desc.value().set(obj->getDenseOrTypedArrayElement(JSID_TO_INT(id)));
1784 } else {
1785 if (!NativeGetExistingProperty(cx, obj, obj, shape, desc.value()))
1786 return false;
1787 }
1788 }
1789
1790 desc.object().set(obj);
1791 desc.assertComplete();
1792 return true;
1793 }
1794
1795
1796 /*** [[Get]] *************************************************************************************/
1797
1798 static inline bool
CallGetter(JSContext * cx,HandleObject obj,HandleValue receiver,HandleShape shape,MutableHandleValue vp)1799 CallGetter(JSContext* cx, HandleObject obj, HandleValue receiver, HandleShape shape,
1800 MutableHandleValue vp)
1801 {
1802 MOZ_ASSERT(!shape->hasDefaultGetter());
1803
1804 if (shape->hasGetterValue()) {
1805 RootedValue getter(cx, shape->getterValue());
1806 return js::CallGetter(cx, receiver, getter, vp);
1807 }
1808
1809 // In contrast to normal getters JSGetterOps always want the holder.
1810 RootedId id(cx, shape->propid());
1811 return CallJSGetterOp(cx, shape->getterOp(), obj, id, vp);
1812 }
1813
1814 template <AllowGC allowGC>
1815 static MOZ_ALWAYS_INLINE bool
GetExistingProperty(JSContext * cx,typename MaybeRooted<Value,allowGC>::HandleType receiver,typename MaybeRooted<NativeObject *,allowGC>::HandleType obj,typename MaybeRooted<Shape *,allowGC>::HandleType shape,typename MaybeRooted<Value,allowGC>::MutableHandleType vp)1816 GetExistingProperty(JSContext* cx,
1817 typename MaybeRooted<Value, allowGC>::HandleType receiver,
1818 typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
1819 typename MaybeRooted<Shape*, allowGC>::HandleType shape,
1820 typename MaybeRooted<Value, allowGC>::MutableHandleType vp)
1821 {
1822 if (shape->hasSlot()) {
1823 vp.set(obj->getSlot(shape->slot()));
1824 MOZ_ASSERT_IF(!vp.isMagic(JS_UNINITIALIZED_LEXICAL) &&
1825 !obj->isSingleton() &&
1826 !obj->template is<EnvironmentObject>() &&
1827 shape->hasDefaultGetter(),
1828 ObjectGroupHasProperty(cx, obj->group(), shape->propid(), vp));
1829 } else {
1830 vp.setUndefined();
1831 }
1832 if (shape->hasDefaultGetter())
1833 return true;
1834
1835 {
1836 jsbytecode* pc;
1837 JSScript* script = cx->currentScript(&pc);
1838 if (script && script->hasBaselineScript()) {
1839 switch (JSOp(*pc)) {
1840 case JSOP_GETPROP:
1841 case JSOP_CALLPROP:
1842 case JSOP_LENGTH:
1843 script->baselineScript()->noteAccessedGetter(script->pcToOffset(pc));
1844 break;
1845 default:
1846 break;
1847 }
1848 }
1849 }
1850
1851 if (!allowGC)
1852 return false;
1853
1854 if (!CallGetter(cx,
1855 MaybeRooted<JSObject*, allowGC>::toHandle(obj),
1856 MaybeRooted<Value, allowGC>::toHandle(receiver),
1857 MaybeRooted<Shape*, allowGC>::toHandle(shape),
1858 MaybeRooted<Value, allowGC>::toMutableHandle(vp)))
1859 {
1860 return false;
1861 }
1862
1863 // Ancient nonstandard extension: via the JSAPI it's possible to create a
1864 // data property that has both a slot and a getter. In that case, copy the
1865 // value returned by the getter back into the slot.
1866 if (shape->hasSlot() && obj->contains(cx, shape))
1867 obj->setSlot(shape->slot(), vp);
1868
1869 return true;
1870 }
1871
1872 bool
NativeGetExistingProperty(JSContext * cx,HandleObject receiver,HandleNativeObject obj,HandleShape shape,MutableHandleValue vp)1873 js::NativeGetExistingProperty(JSContext* cx, HandleObject receiver, HandleNativeObject obj,
1874 HandleShape shape, MutableHandleValue vp)
1875 {
1876 RootedValue receiverValue(cx, ObjectValue(*receiver));
1877 return GetExistingProperty<CanGC>(cx, receiverValue, obj, shape, vp);
1878 }
1879
1880 /*
1881 * Given pc pointing after a property accessing bytecode, return true if the
1882 * access is "property-detecting" -- that is, if we shouldn't warn about it
1883 * even if no such property is found and strict warnings are enabled.
1884 */
1885 static bool
Detecting(JSContext * cx,JSScript * script,jsbytecode * pc)1886 Detecting(JSContext* cx, JSScript* script, jsbytecode* pc)
1887 {
1888 MOZ_ASSERT(script->containsPC(pc));
1889
1890 // Skip jump target opcodes.
1891 while (pc < script->codeEnd() && BytecodeIsJumpTarget(JSOp(*pc)))
1892 pc = GetNextPc(pc);
1893
1894 MOZ_ASSERT(script->containsPC(pc));
1895 if (pc >= script->codeEnd())
1896 return false;
1897
1898 // General case: a branch or equality op follows the access.
1899 JSOp op = JSOp(*pc);
1900 if (CodeSpec[op].format & JOF_DETECTING)
1901 return true;
1902
1903 jsbytecode* endpc = script->codeEnd();
1904
1905 if (op == JSOP_NULL) {
1906 // Special case #1: don't warn about (obj.prop == null).
1907 if (++pc < endpc) {
1908 op = JSOp(*pc);
1909 return op == JSOP_EQ || op == JSOP_NE;
1910 }
1911 return false;
1912 }
1913
1914 if (op == JSOP_GETGNAME || op == JSOP_GETNAME) {
1915 // Special case #2: don't warn about (obj.prop == undefined).
1916 JSAtom* atom = script->getAtom(GET_UINT32_INDEX(pc));
1917 if (atom == cx->names().undefined &&
1918 (pc += CodeSpec[op].length) < endpc) {
1919 op = JSOp(*pc);
1920 return op == JSOP_EQ || op == JSOP_NE || op == JSOP_STRICTEQ || op == JSOP_STRICTNE;
1921 }
1922 }
1923
1924 return false;
1925 }
1926
1927 enum IsNameLookup { NotNameLookup = false, NameLookup = true };
1928
1929 /*
1930 * Finish getting the property `receiver[id]` after looking at every object on
1931 * the prototype chain and not finding any such property.
1932 *
1933 * Per the spec, this should just set the result to `undefined` and call it a
1934 * day. However:
1935 *
1936 * 1. We add support for the nonstandard JSClass::getProperty hook.
1937 *
1938 * 2. This function also runs when we're evaluating an expression that's an
1939 * Identifier (that is, an unqualified name lookup), so we need to figure
1940 * out if that's what's happening and throw a ReferenceError if so.
1941 *
1942 * 3. We also emit an optional warning for this. (It's not super useful on the
1943 * web, as there are too many false positives, but anecdotally useful in
1944 * Gecko code.)
1945 */
1946 static bool
GetNonexistentProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue receiver,IsNameLookup nameLookup,MutableHandleValue vp)1947 GetNonexistentProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
1948 HandleValue receiver, IsNameLookup nameLookup, MutableHandleValue vp)
1949 {
1950 vp.setUndefined();
1951
1952 // Non-standard extension: Call the getProperty hook. If it sets vp to a
1953 // value other than undefined, we're done. If not, fall through to the
1954 // warning/error checks below.
1955 if (JSGetterOp getProperty = obj->getClass()->getGetProperty()) {
1956 if (!CallJSGetterOp(cx, getProperty, obj, id, vp))
1957 return false;
1958
1959 if (!vp.isUndefined())
1960 return true;
1961 }
1962
1963 // If we are doing a name lookup, this is a ReferenceError.
1964 if (nameLookup)
1965 return ReportIsNotDefined(cx, id);
1966
1967 // Give a strict warning if foo.bar is evaluated by a script for an object
1968 // foo with no property named 'bar'.
1969 //
1970 // Don't warn if extra warnings not enabled or for random getprop
1971 // operations.
1972 if (!cx->compartment()->behaviors().extraWarnings(cx))
1973 return true;
1974
1975 jsbytecode* pc;
1976 RootedScript script(cx, cx->currentScript(&pc));
1977 if (!script)
1978 return true;
1979
1980 if (*pc != JSOP_GETPROP && *pc != JSOP_GETELEM)
1981 return true;
1982
1983 // Don't warn repeatedly for the same script.
1984 if (script->warnedAboutUndefinedProp())
1985 return true;
1986
1987 // Don't warn in self-hosted code (where the further presence of
1988 // JS::RuntimeOptions::werror() would result in impossible-to-avoid
1989 // errors to entirely-innocent client code).
1990 if (script->selfHosted())
1991 return true;
1992
1993 // We may just be checking if that object has an iterator.
1994 if (JSID_IS_ATOM(id, cx->names().iteratorIntrinsic))
1995 return true;
1996
1997 // Do not warn about tests like (obj[prop] == undefined).
1998 pc += CodeSpec[*pc].length;
1999 if (Detecting(cx, script, pc))
2000 return true;
2001
2002 unsigned flags = JSREPORT_WARNING | JSREPORT_STRICT;
2003 script->setWarnedAboutUndefinedProp();
2004
2005 // Ok, bad undefined property reference: whine about it.
2006 RootedValue val(cx, IdToValue(id));
2007 return ReportValueErrorFlags(cx, flags, JSMSG_UNDEFINED_PROP, JSDVG_IGNORE_STACK, val,
2008 nullptr, nullptr, nullptr);
2009 }
2010
2011 /* The NoGC version of GetNonexistentProperty, present only to make types line up. */
2012 bool
GetNonexistentProperty(JSContext * cx,NativeObject * const & obj,const jsid & id,const Value & receiver,IsNameLookup nameLookup,FakeMutableHandle<Value> vp)2013 GetNonexistentProperty(JSContext* cx, NativeObject* const& obj, const jsid& id, const Value& receiver,
2014 IsNameLookup nameLookup, FakeMutableHandle<Value> vp)
2015 {
2016 return false;
2017 }
2018
2019 static inline bool
GeneralizedGetProperty(JSContext * cx,HandleObject obj,HandleId id,HandleValue receiver,IsNameLookup nameLookup,MutableHandleValue vp)2020 GeneralizedGetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue receiver,
2021 IsNameLookup nameLookup, MutableHandleValue vp)
2022 {
2023 JS_CHECK_RECURSION(cx, return false);
2024 if (nameLookup) {
2025 // When nameLookup is true, GetProperty implements ES6 rev 34 (2015 Feb
2026 // 20) 8.1.1.2.6 GetBindingValue, with step 3 (the call to HasProperty)
2027 // and step 6 (the call to Get) fused so that only a single lookup is
2028 // needed.
2029 //
2030 // If we get here, we've reached a non-native object. Fall back on the
2031 // algorithm as specified, with two separate lookups. (Note that we
2032 // throw ReferenceErrors regardless of strictness, technically a bug.)
2033
2034 bool found;
2035 if (!HasProperty(cx, obj, id, &found))
2036 return false;
2037 if (!found)
2038 return ReportIsNotDefined(cx, id);
2039 }
2040
2041 return GetProperty(cx, obj, receiver, id, vp);
2042 }
2043
2044 static inline bool
GeneralizedGetProperty(JSContext * cx,JSObject * obj,jsid id,const Value & receiver,IsNameLookup nameLookup,FakeMutableHandle<Value> vp)2045 GeneralizedGetProperty(JSContext* cx, JSObject* obj, jsid id, const Value& receiver,
2046 IsNameLookup nameLookup, FakeMutableHandle<Value> vp)
2047 {
2048 JS_CHECK_RECURSION_DONT_REPORT(cx, return false);
2049 if (nameLookup)
2050 return false;
2051 return GetPropertyNoGC(cx, obj, receiver, id, vp.address());
2052 }
2053
2054 template <AllowGC allowGC>
2055 static MOZ_ALWAYS_INLINE bool
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)2056 NativeGetPropertyInline(JSContext* cx,
2057 typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
2058 typename MaybeRooted<Value, allowGC>::HandleType receiver,
2059 typename MaybeRooted<jsid, allowGC>::HandleType id,
2060 IsNameLookup nameLookup,
2061 typename MaybeRooted<Value, allowGC>::MutableHandleType vp)
2062 {
2063 typename MaybeRooted<NativeObject*, allowGC>::RootType pobj(cx, obj);
2064 typename MaybeRooted<Shape*, allowGC>::RootType shape(cx);
2065
2066 // This loop isn't explicit in the spec algorithm. See the comment on step
2067 // 4.d below.
2068 for (;;) {
2069 // Steps 2-3. ('done' is a SpiderMonkey-specific thing, used below.)
2070 bool done;
2071 if (!LookupOwnPropertyInline<allowGC>(cx, pobj, id, &shape, &done))
2072 return false;
2073
2074 if (shape) {
2075 // Steps 5-8. Special case for dense elements because
2076 // GetExistingProperty doesn't support those.
2077 if (IsImplicitDenseOrTypedArrayElement(shape)) {
2078 vp.set(pobj->getDenseOrTypedArrayElement(JSID_TO_INT(id)));
2079 return true;
2080 }
2081 return GetExistingProperty<allowGC>(cx, receiver, pobj, shape, vp);
2082 }
2083
2084 // Steps 4.a-b. The check for 'done' on this next line is tricky.
2085 // done can be true in exactly these unlikely-sounding cases:
2086 // - We're looking up an element, and pobj is a TypedArray that
2087 // doesn't have that many elements.
2088 // - We're being called from a resolve hook to assign to the property
2089 // being resolved.
2090 // What they all have in common is we do not want to keep walking
2091 // the prototype chain.
2092 RootedObject proto(cx, done ? nullptr : pobj->staticPrototype());
2093
2094 // Step 4.c. The spec algorithm simply returns undefined if proto is
2095 // null, but see the comment on GetNonexistentProperty.
2096 if (!proto)
2097 return GetNonexistentProperty(cx, obj, id, receiver, nameLookup, vp);
2098
2099 // Step 4.d. If the prototype is also native, this step is a
2100 // recursive tail call, and we don't need to go through all the
2101 // plumbing of JSObject::getGeneric; the top of the loop is where
2102 // we're going to end up anyway. But if pobj is non-native,
2103 // that optimization would be incorrect.
2104 if (proto->getOpsGetProperty())
2105 return GeneralizedGetProperty(cx, proto, id, receiver, nameLookup, vp);
2106
2107 pobj = &proto->as<NativeObject>();
2108 }
2109 }
2110
2111 bool
NativeGetProperty(JSContext * cx,HandleNativeObject obj,HandleValue receiver,HandleId id,MutableHandleValue vp)2112 js::NativeGetProperty(JSContext* cx, HandleNativeObject obj, HandleValue receiver, HandleId id,
2113 MutableHandleValue vp)
2114 {
2115 return NativeGetPropertyInline<CanGC>(cx, obj, receiver, id, NotNameLookup, vp);
2116 }
2117
2118 bool
NativeGetPropertyNoGC(JSContext * cx,NativeObject * obj,const Value & receiver,jsid id,Value * vp)2119 js::NativeGetPropertyNoGC(JSContext* cx, NativeObject* obj, const Value& receiver, jsid id, Value* vp)
2120 {
2121 AutoAssertNoException noexc(cx);
2122 return NativeGetPropertyInline<NoGC>(cx, obj, receiver, id, NotNameLookup, vp);
2123 }
2124
2125 bool
GetPropertyForNameLookup(JSContext * cx,HandleObject obj,HandleId id,MutableHandleValue vp)2126 js::GetPropertyForNameLookup(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp)
2127 {
2128 RootedValue receiver(cx, ObjectValue(*obj));
2129 if (obj->getOpsGetProperty())
2130 return GeneralizedGetProperty(cx, obj, id, receiver, NameLookup, vp);
2131 return NativeGetPropertyInline<CanGC>(cx, obj.as<NativeObject>(), receiver, id, NameLookup, vp);
2132 }
2133
2134
2135 /*** [[Set]] *************************************************************************************/
2136
2137 static bool
MaybeReportUndeclaredVarAssignment(JSContext * cx,HandleString propname)2138 MaybeReportUndeclaredVarAssignment(JSContext* cx, HandleString propname)
2139 {
2140 unsigned flags;
2141 {
2142 jsbytecode* pc;
2143 JSScript* script = cx->currentScript(&pc, JSContext::ALLOW_CROSS_COMPARTMENT);
2144 if (!script)
2145 return true;
2146
2147 // If the code is not strict and extra warnings aren't enabled, then no
2148 // check is needed.
2149 if (IsStrictSetPC(pc))
2150 flags = JSREPORT_ERROR;
2151 else if (cx->compartment()->behaviors().extraWarnings(cx))
2152 flags = JSREPORT_WARNING | JSREPORT_STRICT;
2153 else
2154 return true;
2155 }
2156
2157 JSAutoByteString bytes;
2158 if (!bytes.encodeUtf8(cx, propname))
2159 return false;
2160 return JS_ReportErrorFlagsAndNumberUTF8(cx, flags, GetErrorMessage, nullptr,
2161 JSMSG_UNDECLARED_VAR, bytes.ptr());
2162 }
2163
2164 /*
2165 * Finish assignment to a shapeful data property of a native object obj. This
2166 * conforms to no standard and there is a lot of legacy baggage here.
2167 */
2168 static bool
NativeSetExistingDataProperty(JSContext * cx,HandleNativeObject obj,HandleShape shape,HandleValue v,HandleValue receiver,ObjectOpResult & result)2169 NativeSetExistingDataProperty(JSContext* cx, HandleNativeObject obj, HandleShape shape,
2170 HandleValue v, HandleValue receiver, ObjectOpResult& result)
2171 {
2172 MOZ_ASSERT(obj->isNative());
2173 MOZ_ASSERT(shape->isDataDescriptor());
2174
2175 if (shape->hasDefaultSetter()) {
2176 if (shape->hasSlot()) {
2177 // The common path. Standard data property.
2178
2179 // Global properties declared with 'var' will be initially
2180 // defined with an undefined value, so don't treat the initial
2181 // assignments to such properties as overwrites.
2182 bool overwriting = !obj->is<GlobalObject>() || !obj->getSlot(shape->slot()).isUndefined();
2183 obj->setSlotWithType(cx, shape, v, overwriting);
2184 return result.succeed();
2185 }
2186
2187 // Bizarre: shared (slotless) property that's writable but has no
2188 // JSSetterOp. JS code can't define such a property, but it can be done
2189 // through the JSAPI. Treat it as non-writable.
2190 return result.fail(JSMSG_GETTER_ONLY);
2191 }
2192
2193 MOZ_ASSERT(!obj->is<WithEnvironmentObject>()); // See bug 1128681.
2194
2195 uint32_t sample = cx->runtime()->propertyRemovals;
2196 RootedId id(cx, shape->propid());
2197 RootedValue value(cx, v);
2198 if (!CallJSSetterOp(cx, shape->setterOp(), obj, id, &value, result))
2199 return false;
2200
2201 // Update any slot for the shape with the value produced by the setter,
2202 // unless the setter deleted the shape.
2203 if (shape->hasSlot() &&
2204 (MOZ_LIKELY(cx->runtime()->propertyRemovals == sample) ||
2205 obj->contains(cx, shape)))
2206 {
2207 obj->setSlot(shape->slot(), value);
2208 }
2209
2210 return true; // result is populated by CallJSSetterOp above.
2211 }
2212
2213 /*
2214 * When a [[Set]] operation finds no existing property with the given id
2215 * or finds a writable data property on the prototype chain, we end up here.
2216 * Finish the [[Set]] by defining a new property on receiver.
2217 *
2218 * This implements ES6 draft rev 28, 9.1.9 [[Set]] steps 5.b-f, but it
2219 * is really old code and there are a few barnacles.
2220 */
2221 bool
SetPropertyByDefining(JSContext * cx,HandleId id,HandleValue v,HandleValue receiverValue,ObjectOpResult & result)2222 js::SetPropertyByDefining(JSContext* cx, HandleId id, HandleValue v, HandleValue receiverValue,
2223 ObjectOpResult& result)
2224 {
2225 // Step 5.b.
2226 if (!receiverValue.isObject())
2227 return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER);
2228 RootedObject receiver(cx, &receiverValue.toObject());
2229
2230 bool existing;
2231 {
2232 // Steps 5.c-d.
2233 Rooted<PropertyDescriptor> desc(cx);
2234 if (!GetOwnPropertyDescriptor(cx, receiver, id, &desc))
2235 return false;
2236
2237 existing = !!desc.object();
2238
2239 // Step 5.e.
2240 if (existing) {
2241 // Step 5.e.i.
2242 if (desc.isAccessorDescriptor())
2243 return result.fail(JSMSG_OVERWRITING_ACCESSOR);
2244
2245 // Step 5.e.ii.
2246 if (!desc.writable())
2247 return result.fail(JSMSG_READ_ONLY);
2248 }
2249 }
2250
2251 // Invalidate SpiderMonkey-specific caches or bail.
2252 const Class* clasp = receiver->getClass();
2253
2254 // Purge the property cache of now-shadowed id in receiver's environment chain.
2255 if (!PurgeEnvironmentChain(cx, receiver, id))
2256 return false;
2257
2258 // Steps 5.e.iii-iv. and 5.f.i. Define the new data property.
2259 unsigned attrs =
2260 existing
2261 ? JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY | JSPROP_IGNORE_PERMANENT
2262 : JSPROP_ENUMERATE;
2263 JSGetterOp getter = clasp->getGetProperty();
2264 JSSetterOp setter = clasp->getSetProperty();
2265 MOZ_ASSERT(getter != JS_PropertyStub);
2266 MOZ_ASSERT(setter != JS_StrictPropertyStub);
2267 if (!DefineProperty(cx, receiver, id, v, getter, setter, attrs, result))
2268 return false;
2269
2270 // If the receiver is native, there is one more legacy wrinkle: the class
2271 // JSSetterOp is called after defining the new property.
2272 if (setter && receiver->is<NativeObject>()) {
2273 if (!result)
2274 return true;
2275
2276 Rooted<NativeObject*> nativeReceiver(cx, &receiver->as<NativeObject>());
2277 if (!cx->shouldBeJSContext())
2278 return false;
2279 RootedValue receiverValue(cx, ObjectValue(*receiver));
2280
2281 // This lookup is a bit unfortunate, but not nearly the most
2282 // unfortunate thing about Class getters and setters. Since the above
2283 // DefineProperty call succeeded, receiver is native, and the property
2284 // has a setter (and thus can't be a dense element), this lookup is
2285 // guaranteed to succeed.
2286 RootedShape shape(cx, nativeReceiver->lookup(cx, id));
2287 MOZ_ASSERT(shape);
2288 return NativeSetExistingDataProperty(cx->asJSContext(), nativeReceiver, shape, v,
2289 receiverValue, result);
2290 }
2291
2292 return true;
2293 }
2294
2295 // When setting |id| for |receiver| and |obj| has no property for id, continue
2296 // the search up the prototype chain.
2297 bool
SetPropertyOnProto(JSContext * cx,HandleObject obj,HandleId id,HandleValue v,HandleValue receiver,ObjectOpResult & result)2298 js::SetPropertyOnProto(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
2299 HandleValue receiver, ObjectOpResult& result)
2300 {
2301 MOZ_ASSERT(!obj->is<ProxyObject>());
2302
2303 RootedObject proto(cx, obj->staticPrototype());
2304 if (proto)
2305 return SetProperty(cx, proto, id, v, receiver, result);
2306
2307 return SetPropertyByDefining(cx, id, v, receiver, result);
2308 }
2309
2310 /*
2311 * Implement "the rest of" assignment to a property when no property receiver[id]
2312 * was found anywhere on the prototype chain.
2313 *
2314 * FIXME: This should be updated to follow ES6 draft rev 28, section 9.1.9,
2315 * steps 4.d.i and 5.
2316 */
2317 static bool
SetNonexistentProperty(JSContext * cx,HandleId id,HandleValue v,HandleValue receiver,QualifiedBool qualified,ObjectOpResult & result)2318 SetNonexistentProperty(JSContext* cx, HandleId id, HandleValue v, HandleValue receiver,
2319 QualifiedBool qualified, ObjectOpResult& result)
2320 {
2321 if (!qualified && receiver.isObject() && receiver.toObject().isUnqualifiedVarObj()) {
2322 RootedString idStr(cx, JSID_TO_STRING(id));
2323 if (!MaybeReportUndeclaredVarAssignment(cx, idStr))
2324 return false;
2325 }
2326
2327 return SetPropertyByDefining(cx, id, v, receiver, result);
2328 }
2329
2330 /*
2331 * Set an existing own property obj[index] that's a dense element or typed
2332 * array element.
2333 */
2334 static bool
SetDenseOrTypedArrayElement(JSContext * cx,HandleNativeObject obj,uint32_t index,HandleValue v,ObjectOpResult & result)2335 SetDenseOrTypedArrayElement(JSContext* cx, HandleNativeObject obj, uint32_t index, HandleValue v,
2336 ObjectOpResult& result)
2337 {
2338 if (obj->is<TypedArrayObject>()) {
2339 double d;
2340 if (!ToNumber(cx, v, &d))
2341 return false;
2342
2343 // Silently do nothing for out-of-bounds sets, for consistency with
2344 // current behavior. (ES6 currently says to throw for this in
2345 // strict mode code, so we may eventually need to change.)
2346 uint32_t len = obj->as<TypedArrayObject>().length();
2347 if (index < len)
2348 TypedArrayObject::setElement(obj->as<TypedArrayObject>(), index, d);
2349 return result.succeed();
2350 }
2351
2352 if (WouldDefinePastNonwritableLength(obj, index))
2353 return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
2354
2355 if (!obj->maybeCopyElementsForWrite(cx))
2356 return false;
2357
2358 obj->setDenseElementWithType(cx, index, v);
2359 return result.succeed();
2360 }
2361
2362 /*
2363 * Finish the assignment `receiver[id] = v` when an existing property (shape)
2364 * has been found on a native object (pobj). This implements ES6 draft rev 32
2365 * (2015 Feb 2) 9.1.9 steps 5 and 6.
2366 *
2367 * It is necessary to pass both id and shape because shape could be an implicit
2368 * dense or typed array element (i.e. not actually a pointer to a Shape).
2369 */
2370 static bool
SetExistingProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue v,HandleValue receiver,HandleNativeObject pobj,HandleShape shape,ObjectOpResult & result)2371 SetExistingProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleValue v,
2372 HandleValue receiver, HandleNativeObject pobj, HandleShape shape,
2373 ObjectOpResult& result)
2374 {
2375 // Step 5 for dense elements.
2376 if (IsImplicitDenseOrTypedArrayElement(shape)) {
2377 // Step 5.a.
2378 if (pobj->getElementsHeader()->isFrozen())
2379 return result.fail(JSMSG_READ_ONLY);
2380
2381 // Pure optimization for the common case:
2382 if (receiver.isObject() && pobj == &receiver.toObject())
2383 return SetDenseOrTypedArrayElement(cx, pobj, JSID_TO_INT(id), v, result);
2384
2385 // Steps 5.b-f.
2386 return SetPropertyByDefining(cx, id, v, receiver, result);
2387 }
2388
2389 // Step 5 for all other properties.
2390 if (shape->isDataDescriptor()) {
2391 // Step 5.a.
2392 if (!shape->writable())
2393 return result.fail(JSMSG_READ_ONLY);
2394
2395 // steps 5.c-f.
2396 if (receiver.isObject() && pobj == &receiver.toObject()) {
2397 // Pure optimization for the common case. There's no point performing
2398 // the lookup in step 5.c again, as our caller just did it for us. The
2399 // result is |shape|.
2400
2401 // Steps 5.e.i-ii.
2402 if (pobj->is<ArrayObject>() && id == NameToId(cx->names().length)) {
2403 Rooted<ArrayObject*> arr(cx, &pobj->as<ArrayObject>());
2404 return ArraySetLength(cx, arr, id, shape->attributes(), v, result);
2405 }
2406 return NativeSetExistingDataProperty(cx, pobj, shape, v, receiver, result);
2407 }
2408
2409 // SpiderMonkey special case: assigning to an inherited slotless
2410 // property causes the setter to be called, instead of shadowing,
2411 // unless the existing property is JSPROP_SHADOWABLE (see bug 552432).
2412 if (!shape->hasSlot() && !shape->hasShadowable()) {
2413 // Even weirder sub-special-case: inherited slotless data property
2414 // with default setter. Wut.
2415 if (shape->hasDefaultSetter())
2416 return result.succeed();
2417
2418 RootedValue valCopy(cx, v);
2419 return CallJSSetterOp(cx, shape->setterOp(), obj, id, &valCopy, result);
2420 }
2421
2422 // Shadow pobj[id] by defining a new data property receiver[id].
2423 // Delegate everything to SetPropertyByDefining.
2424 return SetPropertyByDefining(cx, id, v, receiver, result);
2425 }
2426
2427 // Steps 6-11.
2428 MOZ_ASSERT(shape->isAccessorDescriptor());
2429 MOZ_ASSERT_IF(!shape->hasSetterObject(), shape->hasDefaultSetter());
2430 if (shape->hasDefaultSetter())
2431 return result.fail(JSMSG_GETTER_ONLY);
2432
2433 RootedValue setter(cx, ObjectValue(*shape->setterObject()));
2434 if (!js::CallSetter(cx, receiver, setter, v))
2435 return false;
2436
2437 return result.succeed();
2438 }
2439
2440 bool
NativeSetProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue value,HandleValue receiver,QualifiedBool qualified,ObjectOpResult & result)2441 js::NativeSetProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleValue value,
2442 HandleValue receiver, QualifiedBool qualified, ObjectOpResult& result)
2443 {
2444 // Fire watchpoints, if any.
2445 RootedValue v(cx, value);
2446 if (MOZ_UNLIKELY(obj->watched())) {
2447 WatchpointMap* wpmap = cx->compartment()->watchpointMap;
2448 if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, &v))
2449 return false;
2450 }
2451
2452 // Step numbers below reference ES6 rev 27 9.1.9, the [[Set]] internal
2453 // method for ordinary objects. We substitute our own names for these names
2454 // used in the spec: O -> pobj, P -> id, ownDesc -> shape.
2455 RootedShape shape(cx);
2456 RootedNativeObject pobj(cx, obj);
2457
2458 // This loop isn't explicit in the spec algorithm. See the comment on step
2459 // 4.c.i below. (There's a very similar loop in the NativeGetProperty
2460 // implementation, but unfortunately not similar enough to common up.)
2461 for (;;) {
2462 // Steps 2-3. ('done' is a SpiderMonkey-specific thing, used below.)
2463 bool done;
2464 if (!LookupOwnPropertyInline<CanGC>(cx, pobj, id, &shape, &done))
2465 return false;
2466
2467 if (shape) {
2468 // Steps 5-6.
2469 return SetExistingProperty(cx, obj, id, v, receiver, pobj, shape, result);
2470 }
2471
2472 // Steps 4.a-b. The check for 'done' on this next line is tricky.
2473 // done can be true in exactly these unlikely-sounding cases:
2474 // - We're looking up an element, and pobj is a TypedArray that
2475 // doesn't have that many elements.
2476 // - We're being called from a resolve hook to assign to the property
2477 // being resolved.
2478 // What they all have in common is we do not want to keep walking
2479 // the prototype chain.
2480 RootedObject proto(cx, done ? nullptr : pobj->staticPrototype());
2481 if (!proto) {
2482 // Step 4.d.i (and step 5).
2483 return SetNonexistentProperty(cx, id, v, receiver, qualified, result);
2484 }
2485
2486 // Step 4.c.i. If the prototype is also native, this step is a
2487 // recursive tail call, and we don't need to go through all the
2488 // plumbing of SetProperty; the top of the loop is where we're going to
2489 // end up anyway. But if pobj is non-native, that optimization would be
2490 // incorrect.
2491 if (!proto->isNative()) {
2492 // Unqualified assignments are not specified to go through [[Set]]
2493 // at all, but they do go through this function. So check for
2494 // unqualified assignment to a nonexistent global (a strict error).
2495 if (!qualified) {
2496 bool found;
2497 if (!HasProperty(cx, proto, id, &found))
2498 return false;
2499 if (!found)
2500 return SetNonexistentProperty(cx, id, v, receiver, qualified, result);
2501 }
2502
2503 return SetProperty(cx, proto, id, v, receiver, result);
2504 }
2505 pobj = &proto->as<NativeObject>();
2506 }
2507 }
2508
2509 bool
NativeSetElement(JSContext * cx,HandleNativeObject obj,uint32_t index,HandleValue v,HandleValue receiver,ObjectOpResult & result)2510 js::NativeSetElement(JSContext* cx, HandleNativeObject obj, uint32_t index, HandleValue v,
2511 HandleValue receiver, ObjectOpResult& result)
2512 {
2513 RootedId id(cx);
2514 if (!IndexToId(cx, index, &id))
2515 return false;
2516 return NativeSetProperty(cx, obj, id, v, receiver, Qualified, result);
2517 }
2518
2519 /*** [[Delete]] **********************************************************************************/
2520
2521 // ES6 draft rev31 9.1.10 [[Delete]]
2522 bool
NativeDeleteProperty(JSContext * cx,HandleNativeObject obj,HandleId id,ObjectOpResult & result)2523 js::NativeDeleteProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
2524 ObjectOpResult& result)
2525 {
2526 // Steps 2-3.
2527 RootedShape shape(cx);
2528 if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &shape))
2529 return false;
2530
2531 // Step 4.
2532 if (!shape) {
2533 // If no property call the class's delProperty hook, passing succeeded
2534 // as the result parameter. This always succeeds when there is no hook.
2535 return CallJSDeletePropertyOp(cx, obj->getClass()->getDelProperty(), obj, id, result);
2536 }
2537
2538 cx->runtime()->gc.poke();
2539
2540 // Step 6. Non-configurable property.
2541 if (GetShapeAttributes(obj, shape) & JSPROP_PERMANENT)
2542 return result.failCantDelete();
2543
2544 if (!CallJSDeletePropertyOp(cx, obj->getClass()->getDelProperty(), obj, id, result))
2545 return false;
2546 if (!result)
2547 return true;
2548
2549 // Step 5.
2550 if (IsImplicitDenseOrTypedArrayElement(shape)) {
2551 // Typed array elements are non-configurable.
2552 MOZ_ASSERT(!obj->is<TypedArrayObject>());
2553
2554 if (!obj->maybeCopyElementsForWrite(cx))
2555 return false;
2556
2557 obj->setDenseElementHole(cx, JSID_TO_INT(id));
2558 } else {
2559 if (!obj->removeProperty(cx, id))
2560 return false;
2561 }
2562
2563 return SuppressDeletedProperty(cx, obj, id);
2564 }
2565