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