Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
7 /* JS symbol tables. */
9 #include "vm/Shape-inl.h"
11 #include "mozilla/DebugOnly.h"
12 #include "mozilla/MathAlgorithms.h"
13 #include "mozilla/PodOperations.h"
15 #include "jsatom.h"
16 #include "jscntxt.h"
17 #include "jshashutil.h"
18 #include "jsobj.h"
20 #include "js/HashTable.h"
22 #include "jscntxtinlines.h"
23 #include "jsobjinlines.h"
25 #include "vm/ObjectImpl-inl.h"
26 #include "vm/Runtime-inl.h"
28 using namespace js;
29 using namespace js::gc;
31 using mozilla::CeilingLog2Size;
32 using mozilla::DebugOnly;
33 using mozilla::PodZero;
34 using mozilla::RotateLeft;
36 bool
37 ShapeTable::init(ThreadSafeContext *cx, Shape *lastProp)
38 {
39 /*
40 * Either we're creating a table for a large scope that was populated
41 * via property cache hit logic under JSOP_INITPROP, JSOP_SETNAME, or
42 * JSOP_SETPROP; or else calloc failed at least once already. In any
43 * event, let's try to grow, overallocating to hold at least twice the
44 * current population.
45 */
46 uint32_t sizeLog2 = CeilingLog2Size(2 * entryCount);
47 if (sizeLog2 < MIN_SIZE_LOG2)
48 sizeLog2 = MIN_SIZE_LOG2;
50 /*
51 * Use rt->calloc_ for memory accounting and overpressure handling
52 * without OOM reporting. See ShapeTable::change.
53 */
54 entries = (Shape **) cx->calloc_(sizeOfEntries(JS_BIT(sizeLog2)));
55 if (!entries)
56 return false;
58 hashShift = HASH_BITS - sizeLog2;
59 for (Shape::Range<NoGC> r(lastProp); !r.empty(); r.popFront()) {
60 Shape &shape = r.front();
61 JS_ASSERT(cx->isThreadLocal(&shape));
62 Shape **spp = search(shape.propid(), true);
64 /*
65 * Beware duplicate args and arg vs. var conflicts: the youngest shape
66 * (nearest to lastProp) must win. See bug 600067.
67 */
68 if (!SHAPE_FETCH(spp))
69 SHAPE_STORE_PRESERVING_COLLISION(spp, &shape);
70 }
71 return true;
72 }
74 void
75 Shape::removeFromDictionary(ObjectImpl *obj)
76 {
77 JS_ASSERT(inDictionary());
78 JS_ASSERT(obj->inDictionaryMode());
79 JS_ASSERT(listp);
81 JS_ASSERT(obj->shape_->inDictionary());
82 JS_ASSERT(obj->shape_->listp == &obj->shape_);
84 if (parent)
85 parent->listp = listp;
86 *listp = parent;
87 listp = nullptr;
88 }
90 void
91 Shape::insertIntoDictionary(HeapPtrShape *dictp)
92 {
93 // Don't assert inDictionaryMode() here because we may be called from
94 // JSObject::toDictionaryMode via JSObject::newDictionaryShape.
95 JS_ASSERT(inDictionary());
96 JS_ASSERT(!listp);
98 JS_ASSERT_IF(*dictp, (*dictp)->inDictionary());
99 JS_ASSERT_IF(*dictp, (*dictp)->listp == dictp);
100 JS_ASSERT_IF(*dictp, compartment() == (*dictp)->compartment());
102 setParent(dictp->get());
103 if (parent)
104 parent->listp = &parent;
105 listp = (HeapPtrShape *) dictp;
106 *dictp = this;
107 }
109 bool
110 Shape::makeOwnBaseShape(ThreadSafeContext *cx)
111 {
112 JS_ASSERT(!base()->isOwned());
113 JS_ASSERT(cx->isThreadLocal(this));
114 assertSameCompartmentDebugOnly(cx, compartment());
116 BaseShape *nbase = js_NewGCBaseShape<NoGC>(cx);
117 if (!nbase)
118 return false;
120 new (nbase) BaseShape(StackBaseShape(this));
121 nbase->setOwned(base()->toUnowned());
123 this->base_ = nbase;
125 return true;
126 }
128 void
129 Shape::handoffTableTo(Shape *shape)
130 {
131 JS_ASSERT(inDictionary() && shape->inDictionary());
133 if (this == shape)
134 return;
136 JS_ASSERT(base()->isOwned() && !shape->base()->isOwned());
138 BaseShape *nbase = base();
140 JS_ASSERT_IF(shape->hasSlot(), nbase->slotSpan() > shape->slot());
142 this->base_ = nbase->baseUnowned();
143 nbase->adoptUnowned(shape->base()->toUnowned());
145 shape->base_ = nbase;
146 }
148 /* static */ bool
149 Shape::hashify(ThreadSafeContext *cx, Shape *shape)
150 {
151 JS_ASSERT(!shape->hasTable());
153 if (!shape->ensureOwnBaseShape(cx))
154 return false;
156 ShapeTable *table = cx->new_<ShapeTable>(shape->entryCount());
157 if (!table)
158 return false;
160 if (!table->init(cx, shape)) {
161 js_free(table);
162 return false;
163 }
165 shape->base()->setTable(table);
166 return true;
167 }
169 /*
170 * Double hashing needs the second hash code to be relatively prime to table
171 * size, so we simply make hash2 odd.
172 */
173 #define HASH1(hash0,shift) ((hash0) >> (shift))
174 #define HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1)
176 Shape **
177 ShapeTable::search(jsid id, bool adding)
178 {
179 js::HashNumber hash0, hash1, hash2;
180 int sizeLog2;
181 Shape *stored, *shape, **spp, **firstRemoved;
182 uint32_t sizeMask;
184 JS_ASSERT(entries);
185 JS_ASSERT(!JSID_IS_EMPTY(id));
187 /* Compute the primary hash address. */
188 hash0 = HashId(id);
189 hash1 = HASH1(hash0, hashShift);
190 spp = entries + hash1;
192 /* Miss: return space for a new entry. */
193 stored = *spp;
194 if (SHAPE_IS_FREE(stored))
195 return spp;
197 /* Hit: return entry. */
198 shape = SHAPE_CLEAR_COLLISION(stored);
199 if (shape && shape->propidRaw() == id)
200 return spp;
202 /* Collision: double hash. */
203 sizeLog2 = HASH_BITS - hashShift;
204 hash2 = HASH2(hash0, sizeLog2, hashShift);
205 sizeMask = JS_BITMASK(sizeLog2);
207 #ifdef DEBUG
208 uintptr_t collision_flag = SHAPE_COLLISION;
209 #endif
211 /* Save the first removed entry pointer so we can recycle it if adding. */
212 if (SHAPE_IS_REMOVED(stored)) {
213 firstRemoved = spp;
214 } else {
215 firstRemoved = nullptr;
216 if (adding && !SHAPE_HAD_COLLISION(stored))
217 SHAPE_FLAG_COLLISION(spp, shape);
218 #ifdef DEBUG
219 collision_flag &= uintptr_t(*spp) & SHAPE_COLLISION;
220 #endif
221 }
223 for (;;) {
224 hash1 -= hash2;
225 hash1 &= sizeMask;
226 spp = entries + hash1;
228 stored = *spp;
229 if (SHAPE_IS_FREE(stored))
230 return (adding && firstRemoved) ? firstRemoved : spp;
232 shape = SHAPE_CLEAR_COLLISION(stored);
233 if (shape && shape->propidRaw() == id) {
234 JS_ASSERT(collision_flag);
235 return spp;
236 }
238 if (SHAPE_IS_REMOVED(stored)) {
239 if (!firstRemoved)
240 firstRemoved = spp;
241 } else {
242 if (adding && !SHAPE_HAD_COLLISION(stored))
243 SHAPE_FLAG_COLLISION(spp, shape);
244 #ifdef DEBUG
245 collision_flag &= uintptr_t(*spp) & SHAPE_COLLISION;
246 #endif
247 }
248 }
250 /* NOTREACHED */
251 return nullptr;
252 }
254 bool
255 ShapeTable::change(int log2Delta, ThreadSafeContext *cx)
256 {
257 JS_ASSERT(entries);
259 /*
260 * Grow, shrink, or compress by changing this->entries.
261 */
262 int oldlog2 = HASH_BITS - hashShift;
263 int newlog2 = oldlog2 + log2Delta;
264 uint32_t oldsize = JS_BIT(oldlog2);
265 uint32_t newsize = JS_BIT(newlog2);
266 Shape **newTable = (Shape **) cx->calloc_(sizeOfEntries(newsize));
267 if (!newTable)
268 return false;
270 /* Now that we have newTable allocated, update members. */
271 hashShift = HASH_BITS - newlog2;
272 removedCount = 0;
273 Shape **oldTable = entries;
274 entries = newTable;
276 /* Copy only live entries, leaving removed and free ones behind. */
277 for (Shape **oldspp = oldTable; oldsize != 0; oldspp++) {
278 Shape *shape = SHAPE_FETCH(oldspp);
279 JS_ASSERT(cx->isThreadLocal(shape));
280 if (shape) {
281 Shape **spp = search(shape->propid(), true);
282 JS_ASSERT(SHAPE_IS_FREE(*spp));
283 *spp = shape;
284 }
285 oldsize--;
286 }
288 /* Finally, free the old entries storage. */
289 js_free(oldTable);
290 return true;
291 }
293 bool
294 ShapeTable::grow(ThreadSafeContext *cx)
295 {
296 JS_ASSERT(needsToGrow());
298 uint32_t size = capacity();
299 int delta = removedCount < size >> 2;
301 if (!change(delta, cx) && entryCount + removedCount == size - 1) {
302 js_ReportOutOfMemory(cx);
303 return false;
304 }
305 return true;
306 }
308 /* static */ Shape *
309 Shape::replaceLastProperty(ExclusiveContext *cx, StackBaseShape &base,
310 TaggedProto proto, HandleShape shape)
311 {
312 JS_ASSERT(!shape->inDictionary());
314 if (!shape->parent) {
315 /* Treat as resetting the initial property of the shape hierarchy. */
316 AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
317 return EmptyShape::getInitialShape(cx, base.clasp, proto,
318 base.parent, base.metadata, kind,
319 base.flags & BaseShape::OBJECT_FLAG_MASK);
320 }
322 UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
323 if (!nbase)
324 return nullptr;
326 StackShape child(shape);
327 child.base = nbase;
329 return cx->compartment()->propertyTree.getChild(cx, shape->parent, child);
330 }
332 /*
333 * Get or create a property-tree or dictionary child property of |parent|,
334 * which must be lastProperty() if inDictionaryMode(), else parent must be
335 * one of lastProperty() or lastProperty()->parent.
336 */
337 /* static */ Shape *
338 JSObject::getChildPropertyOnDictionary(ThreadSafeContext *cx, JS::HandleObject obj,
339 HandleShape parent, js::StackShape &child)
340 {
341 /*
342 * Shared properties have no slot, but slot_ will reflect that of parent.
343 * Unshared properties allocate a slot here but may lose it due to a
344 * JS_ClearScope call.
345 */
346 if (!child.hasSlot()) {
347 child.setSlot(parent->maybeSlot());
348 } else {
349 if (child.hasMissingSlot()) {
350 uint32_t slot;
351 if (!allocSlot(cx, obj, &slot))
352 return nullptr;
353 child.setSlot(slot);
354 } else {
355 /*
356 * Slots can only be allocated out of order on objects in
357 * dictionary mode. Otherwise the child's slot must be after the
358 * parent's slot (if it has one), because slot number determines
359 * slot span for objects with that shape. Usually child slot
360 * *immediately* follows parent slot, but there may be a slot gap
361 * when the object uses some -- but not all -- of its reserved
362 * slots to store properties.
363 */
364 JS_ASSERT(obj->inDictionaryMode() ||
365 parent->hasMissingSlot() ||
366 child.slot() == parent->maybeSlot() + 1 ||
367 (parent->maybeSlot() + 1 < JSSLOT_FREE(obj->getClass()) &&
368 child.slot() == JSSLOT_FREE(obj->getClass())));
369 }
370 }
372 RootedShape shape(cx);
374 if (obj->inDictionaryMode()) {
375 JS_ASSERT(parent == obj->lastProperty());
376 RootedGeneric<StackShape*> childRoot(cx, &child);
377 shape = js_NewGCShape(cx);
378 if (!shape)
379 return nullptr;
380 if (childRoot->hasSlot() && childRoot->slot() >= obj->lastProperty()->base()->slotSpan()) {
381 if (!JSObject::setSlotSpan(cx, obj, childRoot->slot() + 1))
382 return nullptr;
383 }
384 shape->initDictionaryShape(*childRoot, obj->numFixedSlots(), &obj->shape_);
385 }
387 return shape;
388 }
390 /* static */ Shape *
391 JSObject::getChildProperty(ExclusiveContext *cx,
392 HandleObject obj, HandleShape parent, StackShape &unrootedChild)
393 {
394 RootedGeneric<StackShape*> child(cx, &unrootedChild);
395 RootedShape shape(cx, getChildPropertyOnDictionary(cx, obj, parent, *child));
397 if (!obj->inDictionaryMode()) {
398 shape = cx->compartment()->propertyTree.getChild(cx, parent, *child);
399 if (!shape)
400 return nullptr;
401 //JS_ASSERT(shape->parent == parent);
402 //JS_ASSERT_IF(parent != lastProperty(), parent == lastProperty()->parent);
403 if (!JSObject::setLastProperty(cx, obj, shape))
404 return nullptr;
405 }
407 return shape;
408 }
410 /* static */ Shape *
411 JSObject::lookupChildProperty(ThreadSafeContext *cx,
412 HandleObject obj, HandleShape parent, StackShape &unrootedChild)
413 {
414 RootedGeneric<StackShape*> child(cx, &unrootedChild);
415 JS_ASSERT(cx->isThreadLocal(obj));
417 RootedShape shape(cx, getChildPropertyOnDictionary(cx, obj, parent, *child));
419 if (!obj->inDictionaryMode()) {
420 shape = cx->compartment_->propertyTree.lookupChild(cx, parent, *child);
421 if (!shape)
422 return nullptr;
423 if (!JSObject::setLastProperty(cx, obj, shape))
424 return nullptr;
425 }
427 return shape;
428 }
430 bool
431 js::ObjectImpl::toDictionaryMode(ThreadSafeContext *cx)
432 {
433 JS_ASSERT(!inDictionaryMode());
435 /* We allocate the shapes from cx->compartment(), so make sure it's right. */
436 JS_ASSERT(cx->isInsideCurrentCompartment(this));
438 /*
439 * This function is thread safe as long as the object is thread local. It
440 * does not modify the shared shapes, and only allocates newly allocated
441 * (and thus also thread local) shapes.
442 */
443 JS_ASSERT(cx->isThreadLocal(this));
445 uint32_t span = slotSpan();
447 Rooted<ObjectImpl*> self(cx, this);
449 /*
450 * Clone the shapes into a new dictionary list. Don't update the
451 * last property of this object until done, otherwise a GC
452 * triggered while creating the dictionary will get the wrong
453 * slot span for this object.
454 */
455 RootedShape root(cx);
456 RootedShape dictionaryShape(cx);
458 RootedShape shape(cx, lastProperty());
459 while (shape) {
460 JS_ASSERT(!shape->inDictionary());
462 Shape *dprop = js_NewGCShape(cx);
463 if (!dprop) {
464 js_ReportOutOfMemory(cx);
465 return false;
466 }
468 HeapPtrShape *listp = dictionaryShape
469 ? &dictionaryShape->parent
470 : (HeapPtrShape *) root.address();
472 StackShape child(shape);
473 dprop->initDictionaryShape(child, self->numFixedSlots(), listp);
475 JS_ASSERT(!dprop->hasTable());
476 dictionaryShape = dprop;
477 shape = shape->previous();
478 }
480 if (!Shape::hashify(cx, root)) {
481 js_ReportOutOfMemory(cx);
482 return false;
483 }
485 JS_ASSERT((Shape **) root->listp == root.address());
486 root->listp = &self->shape_;
487 self->shape_ = root;
489 JS_ASSERT(self->inDictionaryMode());
490 root->base()->setSlotSpan(span);
492 return true;
493 }
495 /*
496 * Normalize stub getter and setter values for faster is-stub testing in the
497 * SHAPE_CALL_[GS]ETTER macros.
498 */
499 static inline bool
500 NormalizeGetterAndSetter(JSObject *obj,
501 jsid id, unsigned attrs, unsigned flags,
502 PropertyOp &getter,
503 StrictPropertyOp &setter)
504 {
505 if (setter == JS_StrictPropertyStub) {
506 JS_ASSERT(!(attrs & JSPROP_SETTER));
507 setter = nullptr;
508 }
509 if (getter == JS_PropertyStub) {
510 JS_ASSERT(!(attrs & JSPROP_GETTER));
511 getter = nullptr;
512 }
514 return true;
515 }
517 /* static */ Shape *
518 JSObject::addProperty(ExclusiveContext *cx, HandleObject obj, HandleId id,
519 PropertyOp getter, StrictPropertyOp setter,
520 uint32_t slot, unsigned attrs,
521 unsigned flags, bool allowDictionary)
522 {
523 JS_ASSERT(!JSID_IS_VOID(id));
525 bool extensible;
526 if (!JSObject::isExtensible(cx, obj, &extensible))
527 return nullptr;
528 if (!extensible) {
529 if (cx->isJSContext())
530 obj->reportNotExtensible(cx->asJSContext());
531 return nullptr;
532 }
534 NormalizeGetterAndSetter(obj, id, attrs, flags, getter, setter);
536 Shape **spp = nullptr;
537 if (obj->inDictionaryMode())
538 spp = obj->lastProperty()->table().search(id, true);
540 return addPropertyInternal<SequentialExecution>(cx, obj, id, getter, setter, slot, attrs,
541 flags, spp, allowDictionary);
542 }
544 static bool
545 ShouldConvertToDictionary(JSObject *obj)
546 {
547 /*
548 * Use a lower limit if this object is likely a hashmap (SETELEM was used
549 * to set properties).
550 */
551 if (obj->hadElementsAccess())
552 return obj->lastProperty()->entryCount() >= PropertyTree::MAX_HEIGHT_WITH_ELEMENTS_ACCESS;
553 return obj->lastProperty()->entryCount() >= PropertyTree::MAX_HEIGHT;
554 }
556 template <ExecutionMode mode>
557 static inline UnownedBaseShape *
558 GetOrLookupUnownedBaseShape(typename ExecutionModeTraits<mode>::ExclusiveContextType cx,
559 StackBaseShape &base)
560 {
561 if (mode == ParallelExecution)
562 return BaseShape::lookupUnowned(cx, base);
563 return BaseShape::getUnowned(cx->asExclusiveContext(), base);
564 }
566 template <ExecutionMode mode>
567 /* static */ Shape *
568 JSObject::addPropertyInternal(typename ExecutionModeTraits<mode>::ExclusiveContextType cx,
569 HandleObject obj, HandleId id,
570 PropertyOp getter, StrictPropertyOp setter,
571 uint32_t slot, unsigned attrs,
572 unsigned flags, Shape **spp,
573 bool allowDictionary)
574 {
575 JS_ASSERT(cx->isThreadLocal(obj));
576 JS_ASSERT_IF(!allowDictionary, !obj->inDictionaryMode());
578 AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter);
580 /*
581 * The code below deals with either converting obj to dictionary mode or
582 * growing an object that's already in dictionary mode. Either way,
583 * dictionray operations are safe if thread local.
584 */
585 ShapeTable *table = nullptr;
586 if (!obj->inDictionaryMode()) {
587 bool stableSlot =
588 (slot == SHAPE_INVALID_SLOT) ||
589 obj->lastProperty()->hasMissingSlot() ||
590 (slot == obj->lastProperty()->maybeSlot() + 1);
591 JS_ASSERT_IF(!allowDictionary, stableSlot);
592 if (allowDictionary &&
593 (!stableSlot || ShouldConvertToDictionary(obj)))
594 {
595 if (!obj->toDictionaryMode(cx))
596 return nullptr;
597 table = &obj->lastProperty()->table();
598 spp = table->search(id, true);
599 }
600 } else {
601 table = &obj->lastProperty()->table();
602 if (table->needsToGrow()) {
603 if (!table->grow(cx))
604 return nullptr;
605 spp = table->search(id, true);
606 JS_ASSERT(!SHAPE_FETCH(spp));
607 }
608 }
610 JS_ASSERT(!!table == !!spp);
612 /* Find or create a property tree node labeled by our arguments. */
613 RootedShape shape(cx);
614 {
615 RootedShape last(cx, obj->lastProperty());
617 uint32_t index;
618 bool indexed = js_IdIsIndex(id, &index);
620 Rooted<UnownedBaseShape*> nbase(cx);
621 if (last->base()->matchesGetterSetter(getter, setter) && !indexed) {
622 nbase = last->base()->unowned();
623 } else {
624 StackBaseShape base(last->base());
625 base.updateGetterSetter(attrs, getter, setter);
626 if (indexed)
627 base.flags |= BaseShape::INDEXED;
628 nbase = GetOrLookupUnownedBaseShape<mode>(cx, base);
629 if (!nbase)
630 return nullptr;
631 }
633 StackShape child(nbase, id, slot, attrs, flags);
634 shape = getOrLookupChildProperty<mode>(cx, obj, last, child);
635 }
637 if (shape) {
638 JS_ASSERT(shape == obj->lastProperty());
640 if (table) {
641 /* Store the tree node pointer in the table entry for id. */
642 SHAPE_STORE_PRESERVING_COLLISION(spp, static_cast<Shape *>(shape));
643 ++table->entryCount;
645 /* Pass the table along to the new last property, namely shape. */
646 JS_ASSERT(&shape->parent->table() == table);
647 shape->parent->handoffTableTo(shape);
648 }
650 obj->checkShapeConsistency();
651 return shape;
652 }
654 obj->checkShapeConsistency();
655 return nullptr;
656 }
658 template /* static */ Shape *
659 JSObject::addPropertyInternal<SequentialExecution>(ExclusiveContext *cx,
660 HandleObject obj, HandleId id,
661 PropertyOp getter, StrictPropertyOp setter,
662 uint32_t slot, unsigned attrs,
663 unsigned flags, Shape **spp,
664 bool allowDictionary);
665 template /* static */ Shape *
666 JSObject::addPropertyInternal<ParallelExecution>(ForkJoinContext *cx,
667 HandleObject obj, HandleId id,
668 PropertyOp getter, StrictPropertyOp setter,
669 uint32_t slot, unsigned attrs,
670 unsigned flags, Shape **spp,
671 bool allowDictionary);
673 JSObject *
674 js::NewReshapedObject(JSContext *cx, HandleTypeObject type, JSObject *parent,
675 gc::AllocKind allocKind, HandleShape shape, NewObjectKind newKind)
676 {
677 RootedObject res(cx, NewObjectWithType(cx, type, parent, allocKind, newKind));
678 if (!res)
679 return nullptr;
681 if (shape->isEmptyShape())
682 return res;
684 /* Get all the ids in the object, in order. */
685 js::AutoIdVector ids(cx);
686 {
687 for (unsigned i = 0; i <= shape->slot(); i++) {
688 if (!ids.append(JSID_VOID))
689 return nullptr;
690 }
691 Shape *nshape = shape;
692 while (!nshape->isEmptyShape()) {
693 ids[nshape->slot()] = nshape->propid();
694 nshape = nshape->previous();
695 }
696 }
698 /* Construct the new shape, without updating type information. */
699 RootedId id(cx);
700 RootedShape newShape(cx, res->lastProperty());
701 for (unsigned i = 0; i < ids.length(); i++) {
702 id = ids[i];
703 JS_ASSERT(!res->nativeContains(cx, id));
705 uint32_t index;
706 bool indexed = js_IdIsIndex(id, &index);
708 Rooted<UnownedBaseShape*> nbase(cx, newShape->base()->unowned());
709 if (indexed) {
710 StackBaseShape base(nbase);
711 base.flags |= BaseShape::INDEXED;
712 nbase = GetOrLookupUnownedBaseShape<SequentialExecution>(cx, base);
713 if (!nbase)
714 return nullptr;
715 }
717 StackShape child(nbase, id, i, JSPROP_ENUMERATE, 0);
718 newShape = cx->compartment()->propertyTree.getChild(cx, newShape, child);
719 if (!newShape)
720 return nullptr;
721 if (!JSObject::setLastProperty(cx, res, newShape))
722 return nullptr;
723 }
725 return res;
726 }
728 /*
729 * Check and adjust the new attributes for the shape to make sure that our
730 * slot access optimizations are sound. It is responsibility of the callers to
731 * enforce all restrictions from ECMA-262 v5 8.12.9 [[DefineOwnProperty]].
732 */
733 static inline bool
734 CheckCanChangeAttrs(ThreadSafeContext *cx, JSObject *obj, Shape *shape, unsigned *attrsp)
735 {
736 if (shape->configurable())
737 return true;
739 /* A permanent property must stay permanent. */
740 *attrsp |= JSPROP_PERMANENT;
742 /* Reject attempts to remove a slot from the permanent data property. */
743 if (shape->isDataDescriptor() && shape->hasSlot() &&
744 (*attrsp & (JSPROP_GETTER | JSPROP_SETTER | JSPROP_SHARED)))
745 {
746 if (cx->isJSContext())
747 obj->reportNotConfigurable(cx->asJSContext(), shape->propid());
748 return false;
749 }
751 return true;
752 }
754 template <ExecutionMode mode>
755 /* static */ Shape *
756 JSObject::putProperty(typename ExecutionModeTraits<mode>::ExclusiveContextType cx,
757 HandleObject obj, HandleId id,
758 PropertyOp getter, StrictPropertyOp setter,
759 uint32_t slot, unsigned attrs, unsigned flags)
760 {
761 JS_ASSERT(cx->isThreadLocal(obj));
762 JS_ASSERT(!JSID_IS_VOID(id));
764 #ifdef DEBUG
765 if (obj->is<ArrayObject>()) {
766 ArrayObject *arr = &obj->as<ArrayObject>();
767 uint32_t index;
768 if (js_IdIsIndex(id, &index))
769 JS_ASSERT(index < arr->length() || arr->lengthIsWritable());
770 }
771 #endif
773 NormalizeGetterAndSetter(obj, id, attrs, flags, getter, setter);
775 AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter);
777 /*
778 * Search for id in order to claim its entry if table has been allocated.
779 *
780 * Note that we can only try to claim an entry in a table that is thread
781 * local. An object may be thread local *without* its shape being thread
782 * local. The only thread local objects that *also* have thread local
783 * shapes are dictionaries that were allocated/converted thread
784 * locally. Only for those objects we can try to claim an entry in its
785 * shape table.
786 */
787 Shape **spp;
788 RootedShape shape(cx, (mode == ParallelExecution
789 ? Shape::searchThreadLocal(cx, obj->lastProperty(), id, &spp,
790 cx->isThreadLocal(obj->lastProperty()))
791 : Shape::search(cx->asExclusiveContext(), obj->lastProperty(), id,
792 &spp, true)));
793 if (!shape) {
794 /*
795 * You can't add properties to a non-extensible object, but you can change
796 * attributes of properties in such objects.
797 */
798 bool extensible;
800 if (mode == ParallelExecution) {
801 if (obj->is<ProxyObject>())
802 return nullptr;
803 extensible = obj->nonProxyIsExtensible();
804 } else {
805 if (!JSObject::isExtensible(cx->asExclusiveContext(), obj, &extensible))
806 return nullptr;
807 }
809 if (!extensible) {
810 if (cx->isJSContext())
811 obj->reportNotExtensible(cx->asJSContext());
812 return nullptr;
813 }
815 return addPropertyInternal<mode>(cx, obj, id, getter, setter, slot, attrs, flags,
816 spp, true);
817 }
819 /* Property exists: search must have returned a valid *spp. */
820 JS_ASSERT_IF(spp, !SHAPE_IS_REMOVED(*spp));
822 if (!CheckCanChangeAttrs(cx, obj, shape, &attrs))
823 return nullptr;
825 /*
826 * If the caller wants to allocate a slot, but doesn't care which slot,
827 * copy the existing shape's slot into slot so we can match shape, if all
828 * other members match.
829 */
830 bool hadSlot = shape->hasSlot();
831 uint32_t oldSlot = shape->maybeSlot();
832 if (!(attrs & JSPROP_SHARED) && slot == SHAPE_INVALID_SLOT && hadSlot)
833 slot = oldSlot;
835 Rooted<UnownedBaseShape*> nbase(cx);
836 {
837 uint32_t index;
838 bool indexed = js_IdIsIndex(id, &index);
839 StackBaseShape base(obj->lastProperty()->base());
840 base.updateGetterSetter(attrs, getter, setter);
841 if (indexed)
842 base.flags |= BaseShape::INDEXED;
843 nbase = GetOrLookupUnownedBaseShape<mode>(cx, base);
844 if (!nbase)
845 return nullptr;
846 }
848 /*
849 * Now that we've possibly preserved slot, check whether all members match.
850 * If so, this is a redundant "put" and we can return without more work.
851 */
852 if (shape->matchesParamsAfterId(nbase, slot, attrs, flags))
853 return shape;
855 /*
856 * Overwriting a non-last property requires switching to dictionary mode.
857 * The shape tree is shared immutable, and we can't removeProperty and then
858 * addPropertyInternal because a failure under add would lose data.
859 */
860 if (shape != obj->lastProperty() && !obj->inDictionaryMode()) {
861 if (!obj->toDictionaryMode(cx))
862 return nullptr;
863 spp = obj->lastProperty()->table().search(shape->propid(), false);
864 shape = SHAPE_FETCH(spp);
865 }
867 JS_ASSERT_IF(shape->hasSlot() && !(attrs & JSPROP_SHARED), shape->slot() == slot);
869 if (obj->inDictionaryMode()) {
870 /*
871 * Updating some property in a dictionary-mode object. Create a new
872 * shape for the existing property, and also generate a new shape for
873 * the last property of the dictionary (unless the modified property
874 * is also the last property).
875 */
876 bool updateLast = (shape == obj->lastProperty());
877 shape = obj->replaceWithNewEquivalentShape(cx, shape);
878 if (!shape)
879 return nullptr;
880 if (!updateLast && !obj->generateOwnShape(cx))
881 return nullptr;
883 /* FIXME bug 593129 -- slot allocation and JSObject *this must move out of here! */
884 if (slot == SHAPE_INVALID_SLOT && !(attrs & JSPROP_SHARED)) {
885 if (!allocSlot(cx, obj, &slot))
886 return nullptr;
887 }
889 if (updateLast)
890 shape->base()->adoptUnowned(nbase);
891 else
892 shape->base_ = nbase;
894 JS_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED);
896 shape->setSlot(slot);
897 shape->attrs = uint8_t(attrs);
898 shape->flags = flags | Shape::IN_DICTIONARY;
899 } else {
900 /*
901 * Updating the last property in a non-dictionary-mode object. Find an
902 * alternate shared child of the last property's previous shape.
903 */
904 StackBaseShape base(obj->lastProperty()->base());
905 base.updateGetterSetter(attrs, getter, setter);
907 UnownedBaseShape *nbase = GetOrLookupUnownedBaseShape<mode>(cx, base);
908 if (!nbase)
909 return nullptr;
911 JS_ASSERT(shape == obj->lastProperty());
913 /* Find or create a property tree node labeled by our arguments. */
914 StackShape child(nbase, id, slot, attrs, flags);
915 RootedShape parent(cx, shape->parent);
916 Shape *newShape = getOrLookupChildProperty<mode>(cx, obj, parent, child);
918 if (!newShape) {
919 obj->checkShapeConsistency();
920 return nullptr;
921 }
923 shape = newShape;
924 }
926 /*
927 * Can't fail now, so free the previous incarnation's slot if the new shape
928 * has no slot. But we do not need to free oldSlot (and must not, as trying
929 * to will botch an assertion in JSObject::freeSlot) if the new last
930 * property (shape here) has a slotSpan that does not cover it.
931 */
932 if (hadSlot && !shape->hasSlot()) {
933 if (oldSlot < obj->slotSpan())
934 obj->freeSlot(oldSlot);
935 /* Note: The optimization based on propertyRemovals is only relevant to the main thread. */
936 if (cx->isJSContext())
937 ++cx->asJSContext()->runtime()->propertyRemovals;
938 }
940 obj->checkShapeConsistency();
942 return shape;
943 }
945 template /* static */ Shape *
946 JSObject::putProperty<SequentialExecution>(ExclusiveContext *cx,
947 HandleObject obj, HandleId id,
948 PropertyOp getter, StrictPropertyOp setter,
949 uint32_t slot, unsigned attrs,
950 unsigned flags);
951 template /* static */ Shape *
952 JSObject::putProperty<ParallelExecution>(ForkJoinContext *cx,
953 HandleObject obj, HandleId id,
954 PropertyOp getter, StrictPropertyOp setter,
955 uint32_t slot, unsigned attrs,
956 unsigned flags);
958 template <ExecutionMode mode>
959 /* static */ Shape *
960 JSObject::changeProperty(typename ExecutionModeTraits<mode>::ExclusiveContextType cx,
961 HandleObject obj, HandleShape shape, unsigned attrs,
962 unsigned mask, PropertyOp getter, StrictPropertyOp setter)
963 {
964 JS_ASSERT(cx->isThreadLocal(obj));
965 JS_ASSERT(obj->nativeContainsPure(shape));
967 attrs |= shape->attrs & mask;
968 JS_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED);
970 /* Allow only shared (slotless) => unshared (slotful) transition. */
971 JS_ASSERT(!((attrs ^ shape->attrs) & JSPROP_SHARED) ||
972 !(attrs & JSPROP_SHARED));
974 if (mode == ParallelExecution) {
975 if (!types::IsTypePropertyIdMarkedNonData(obj, shape->propid()))
976 return nullptr;
977 } else {
978 types::MarkTypePropertyNonData(cx->asExclusiveContext(), obj, shape->propid());
979 }
981 if (getter == JS_PropertyStub)
982 getter = nullptr;
983 if (setter == JS_StrictPropertyStub)
984 setter = nullptr;
986 if (!CheckCanChangeAttrs(cx, obj, shape, &attrs))
987 return nullptr;
989 if (shape->attrs == attrs && shape->getter() == getter && shape->setter() == setter)
990 return shape;
992 /*
993 * Let JSObject::putProperty handle this |overwriting| case, including
994 * the conservation of shape->slot (if it's valid). We must not call
995 * removeProperty because it will free an allocated shape->slot, and
996 * putProperty won't re-allocate it.
997 */
998 RootedId propid(cx, shape->propid());
999 Shape *newShape = putProperty<mode>(cx, obj, propid, getter, setter,
1000 shape->maybeSlot(), attrs, shape->flags);
1002 obj->checkShapeConsistency();
1003 return newShape;
1004 }
1006 template /* static */ Shape *
1007 JSObject::changeProperty<SequentialExecution>(ExclusiveContext *cx,
1008 HandleObject obj, HandleShape shape,
1009 unsigned attrs, unsigned mask,
1010 PropertyOp getter, StrictPropertyOp setter);
1011 template /* static */ Shape *
1012 JSObject::changeProperty<ParallelExecution>(ForkJoinContext *cx,
1013 HandleObject obj, HandleShape shape,
1014 unsigned attrs, unsigned mask,
1015 PropertyOp getter, StrictPropertyOp setter);
1017 bool
1018 JSObject::removeProperty(ExclusiveContext *cx, jsid id_)
1019 {
1020 RootedId id(cx, id_);
1021 RootedObject self(cx, this);
1023 Shape **spp;
1024 RootedShape shape(cx, Shape::search(cx, lastProperty(), id, &spp));
1025 if (!shape)
1026 return true;
1028 /*
1029 * If shape is not the last property added, or the last property cannot
1030 * be removed, switch to dictionary mode.
1031 */
1032 if (!self->inDictionaryMode() && (shape != self->lastProperty() || !self->canRemoveLastProperty())) {
1033 if (!self->toDictionaryMode(cx))
1034 return false;
1035 spp = self->lastProperty()->table().search(shape->propid(), false);
1036 shape = SHAPE_FETCH(spp);
1037 }
1039 /*
1040 * If in dictionary mode, get a new shape for the last property after the
1041 * removal. We need a fresh shape for all dictionary deletions, even of
1042 * the last property. Otherwise, a shape could replay and caches might
1043 * return deleted DictionaryShapes! See bug 595365. Do this before changing
1044 * the object or table, so the remaining removal is infallible.
1045 */
1046 RootedShape spare(cx);
1047 if (self->inDictionaryMode()) {
1048 spare = js_NewGCShape(cx);
1049 if (!spare)
1050 return false;
1051 new (spare) Shape(shape->base()->unowned(), 0);
1052 if (shape == self->lastProperty()) {
1053 /*
1054 * Get an up to date unowned base shape for the new last property
1055 * when removing the dictionary's last property. Information in
1056 * base shapes for non-last properties may be out of sync with the
1057 * object's state.
1058 */
1059 RootedShape previous(cx, self->lastProperty()->parent);
1060 StackBaseShape base(self->lastProperty()->base());
1061 base.updateGetterSetter(previous->attrs, previous->getter(), previous->setter());
1062 BaseShape *nbase = BaseShape::getUnowned(cx, base);
1063 if (!nbase)
1064 return false;
1065 previous->base_ = nbase;
1066 }
1067 }
1069 /* If shape has a slot, free its slot number. */
1070 if (shape->hasSlot()) {
1071 self->freeSlot(shape->slot());
1072 if (cx->isJSContext())
1073 ++cx->asJSContext()->runtime()->propertyRemovals;
1074 }
1076 /*
1077 * A dictionary-mode object owns mutable, unique shapes on a non-circular
1078 * doubly linked list, hashed by lastProperty()->table. So we can edit the
1079 * list and hash in place.
1080 */
1081 if (self->inDictionaryMode()) {
1082 ShapeTable &table = self->lastProperty()->table();
1084 if (SHAPE_HAD_COLLISION(*spp)) {
1085 *spp = SHAPE_REMOVED;
1086 ++table.removedCount;
1087 --table.entryCount;
1088 } else {
1089 *spp = nullptr;
1090 --table.entryCount;
1092 #ifdef DEBUG
1093 /*
1094 * Check the consistency of the table but limit the number of
1095 * checks not to alter significantly the complexity of the
1096 * delete in debug builds, see bug 534493.
1097 */
1098 Shape *aprop = self->lastProperty();
1099 for (int n = 50; --n >= 0 && aprop->parent; aprop = aprop->parent)
1100 JS_ASSERT_IF(aprop != shape, self->nativeContains(cx, aprop));
1101 #endif
1102 }
1104 {
1105 /* Remove shape from its non-circular doubly linked list. */
1106 Shape *oldLastProp = self->lastProperty();
1107 shape->removeFromDictionary(self);
1109 /* Hand off table from the old to new last property. */
1110 oldLastProp->handoffTableTo(self->lastProperty());
1111 }
1113 /* Generate a new shape for the object, infallibly. */
1114 JS_ALWAYS_TRUE(self->generateOwnShape(cx, spare));
1116 /* Consider shrinking table if its load factor is <= .25. */
1117 uint32_t size = table.capacity();
1118 if (size > ShapeTable::MIN_SIZE && table.entryCount <= size >> 2)
1119 (void) table.change(-1, cx);
1120 } else {
1121 /*
1122 * Non-dictionary-mode shape tables are shared immutables, so all we
1123 * need do is retract the last property and we'll either get or else
1124 * lazily make via a later hashify the exact table for the new property
1125 * lineage.
1126 */
1127 JS_ASSERT(shape == self->lastProperty());
1128 self->removeLastProperty(cx);
1129 }
1131 self->checkShapeConsistency();
1132 return true;
1133 }
1135 /* static */ void
1136 JSObject::clear(JSContext *cx, HandleObject obj)
1137 {
1138 RootedShape shape(cx, obj->lastProperty());
1139 JS_ASSERT(obj->inDictionaryMode() == shape->inDictionary());
1141 while (shape->parent) {
1142 shape = shape->parent;
1143 JS_ASSERT(obj->inDictionaryMode() == shape->inDictionary());
1144 }
1145 JS_ASSERT(shape->isEmptyShape());
1147 if (obj->inDictionaryMode())
1148 shape->listp = &obj->shape_;
1150 JS_ALWAYS_TRUE(JSObject::setLastProperty(cx, obj, shape));
1152 ++cx->runtime()->propertyRemovals;
1153 obj->checkShapeConsistency();
1154 }
1156 /* static */ bool
1157 JSObject::rollbackProperties(ExclusiveContext *cx, HandleObject obj, uint32_t slotSpan)
1158 {
1159 /*
1160 * Remove properties from this object until it has a matching slot span.
1161 * The object cannot have escaped in a way which would prevent safe
1162 * removal of the last properties.
1163 */
1164 JS_ASSERT(!obj->inDictionaryMode() && slotSpan <= obj->slotSpan());
1165 while (true) {
1166 if (obj->lastProperty()->isEmptyShape()) {
1167 JS_ASSERT(slotSpan == 0);
1168 break;
1169 } else {
1170 uint32_t slot = obj->lastProperty()->slot();
1171 if (slot < slotSpan)
1172 break;
1173 JS_ASSERT(obj->getSlot(slot).isUndefined());
1174 }
1175 if (!obj->removeProperty(cx, obj->lastProperty()->propid()))
1176 return false;
1177 }
1179 return true;
1180 }
1182 Shape *
1183 ObjectImpl::replaceWithNewEquivalentShape(ThreadSafeContext *cx, Shape *oldShape, Shape *newShape)
1184 {
1185 JS_ASSERT(cx->isThreadLocal(this));
1186 JS_ASSERT(cx->isThreadLocal(oldShape));
1187 JS_ASSERT(cx->isInsideCurrentCompartment(oldShape));
1188 JS_ASSERT_IF(oldShape != lastProperty(),
1189 inDictionaryMode() &&
1190 ((cx->isExclusiveContext()
1191 ? nativeLookup(cx->asExclusiveContext(), oldShape->propidRef())
1192 : nativeLookupPure(oldShape->propidRef())) == oldShape));
1194 ObjectImpl *self = this;
1196 if (!inDictionaryMode()) {
1197 Rooted<ObjectImpl*> selfRoot(cx, self);
1198 RootedShape newRoot(cx, newShape);
1199 if (!toDictionaryMode(cx))
1200 return nullptr;
1201 oldShape = selfRoot->lastProperty();
1202 self = selfRoot;
1203 newShape = newRoot;
1204 }
1206 if (!newShape) {
1207 Rooted<ObjectImpl*> selfRoot(cx, self);
1208 RootedShape oldRoot(cx, oldShape);
1209 newShape = js_NewGCShape(cx);
1210 if (!newShape)
1211 return nullptr;
1212 new (newShape) Shape(oldRoot->base()->unowned(), 0);
1213 self = selfRoot;
1214 oldShape = oldRoot;
1215 }
1217 ShapeTable &table = self->lastProperty()->table();
1218 Shape **spp = oldShape->isEmptyShape()
1219 ? nullptr
1220 : table.search(oldShape->propidRef(), false);
1222 /*
1223 * Splice the new shape into the same position as the old shape, preserving
1224 * enumeration order (see bug 601399).
1225 */
1226 StackShape nshape(oldShape);
1227 newShape->initDictionaryShape(nshape, self->numFixedSlots(), oldShape->listp);
1229 JS_ASSERT(newShape->parent == oldShape);
1230 oldShape->removeFromDictionary(self);
1232 if (newShape == self->lastProperty())
1233 oldShape->handoffTableTo(newShape);
1235 if (spp)
1236 SHAPE_STORE_PRESERVING_COLLISION(spp, newShape);
1237 return newShape;
1238 }
1240 bool
1241 JSObject::shadowingShapeChange(ExclusiveContext *cx, const Shape &shape)
1242 {
1243 return generateOwnShape(cx);
1244 }
1246 /* static */ bool
1247 JSObject::clearParent(JSContext *cx, HandleObject obj)
1248 {
1249 return setParent(cx, obj, NullPtr());
1250 }
1252 /* static */ bool
1253 JSObject::setParent(JSContext *cx, HandleObject obj, HandleObject parent)
1254 {
1255 if (parent && !parent->setDelegate(cx))
1256 return false;
1258 if (obj->inDictionaryMode()) {
1259 StackBaseShape base(obj->lastProperty());
1260 base.parent = parent;
1261 UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
1262 if (!nbase)
1263 return false;
1265 obj->lastProperty()->base()->adoptUnowned(nbase);
1266 return true;
1267 }
1269 Shape *newShape = Shape::setObjectParent(cx, parent, obj->getTaggedProto(), obj->shape_);
1270 if (!newShape)
1271 return false;
1273 obj->shape_ = newShape;
1274 return true;
1275 }
1277 /* static */ Shape *
1278 Shape::setObjectParent(ExclusiveContext *cx, JSObject *parent, TaggedProto proto, Shape *last)
1279 {
1280 if (last->getObjectParent() == parent)
1281 return last;
1283 StackBaseShape base(last);
1284 base.parent = parent;
1286 RootedShape lastRoot(cx, last);
1287 return replaceLastProperty(cx, base, proto, lastRoot);
1288 }
1290 /* static */ bool
1291 JSObject::setMetadata(JSContext *cx, HandleObject obj, HandleObject metadata)
1292 {
1293 if (obj->inDictionaryMode()) {
1294 StackBaseShape base(obj->lastProperty());
1295 base.metadata = metadata;
1296 UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
1297 if (!nbase)
1298 return false;
1300 obj->lastProperty()->base()->adoptUnowned(nbase);
1301 return true;
1302 }
1304 Shape *newShape = Shape::setObjectMetadata(cx, metadata, obj->getTaggedProto(), obj->shape_);
1305 if (!newShape)
1306 return false;
1308 obj->shape_ = newShape;
1309 return true;
1310 }
1312 /* static */ Shape *
1313 Shape::setObjectMetadata(JSContext *cx, JSObject *metadata, TaggedProto proto, Shape *last)
1314 {
1315 if (last->getObjectMetadata() == metadata)
1316 return last;
1318 StackBaseShape base(last);
1319 base.metadata = metadata;
1321 RootedShape lastRoot(cx, last);
1322 return replaceLastProperty(cx, base, proto, lastRoot);
1323 }
1325 /* static */ bool
1326 js::ObjectImpl::preventExtensions(JSContext *cx, Handle<ObjectImpl*> obj)
1327 {
1328 #ifdef DEBUG
1329 bool extensible;
1330 if (!JSObject::isExtensible(cx, obj, &extensible))
1331 return false;
1332 MOZ_ASSERT(extensible,
1333 "Callers must ensure |obj| is extensible before calling "
1334 "preventExtensions");
1335 #endif
1337 if (Downcast(obj)->is<ProxyObject>()) {
1338 RootedObject object(cx, obj->asObjectPtr());
1339 return js::Proxy::preventExtensions(cx, object);
1340 }
1342 RootedObject self(cx, obj->asObjectPtr());
1344 /*
1345 * Force lazy properties to be resolved by iterating over the objects' own
1346 * properties.
1347 */
1348 AutoIdVector props(cx);
1349 if (!js::GetPropertyNames(cx, self, JSITER_HIDDEN | JSITER_OWNONLY, &props))
1350 return false;
1352 /*
1353 * Convert all dense elements to sparse properties. This will shrink the
1354 * initialized length and capacity of the object to zero and ensure that no
1355 * new dense elements can be added without calling growElements(), which
1356 * checks isExtensible().
1357 */
1358 if (self->isNative() && !JSObject::sparsifyDenseElements(cx, self))
1359 return false;
1361 return self->setFlag(cx, BaseShape::NOT_EXTENSIBLE, GENERATE_SHAPE);
1362 }
1364 bool
1365 js::ObjectImpl::setFlag(ExclusiveContext *cx, /*BaseShape::Flag*/ uint32_t flag_,
1366 GenerateShape generateShape)
1367 {
1368 BaseShape::Flag flag = (BaseShape::Flag) flag_;
1370 if (lastProperty()->getObjectFlags() & flag)
1371 return true;
1373 Rooted<ObjectImpl*> self(cx, this);
1375 if (inDictionaryMode()) {
1376 if (generateShape == GENERATE_SHAPE && !generateOwnShape(cx))
1377 return false;
1378 StackBaseShape base(self->lastProperty());
1379 base.flags |= flag;
1380 UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
1381 if (!nbase)
1382 return false;
1384 self->lastProperty()->base()->adoptUnowned(nbase);
1385 return true;
1386 }
1388 Shape *newShape =
1389 Shape::setObjectFlag(cx, flag, self->getTaggedProto(), self->lastProperty());
1390 if (!newShape)
1391 return false;
1393 self->shape_ = newShape;
1394 return true;
1395 }
1397 bool
1398 js::ObjectImpl::clearFlag(ExclusiveContext *cx, /*BaseShape::Flag*/ uint32_t flag)
1399 {
1400 JS_ASSERT(inDictionaryMode());
1401 JS_ASSERT(lastProperty()->getObjectFlags() & flag);
1403 RootedObject self(cx, this->asObjectPtr());
1405 StackBaseShape base(self->lastProperty());
1406 base.flags &= ~flag;
1407 UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
1408 if (!nbase)
1409 return false;
1411 self->lastProperty()->base()->adoptUnowned(nbase);
1412 return true;
1413 }
1415 /* static */ Shape *
1416 Shape::setObjectFlag(ExclusiveContext *cx, BaseShape::Flag flag, TaggedProto proto, Shape *last)
1417 {
1418 if (last->getObjectFlags() & flag)
1419 return last;
1421 StackBaseShape base(last);
1422 base.flags |= flag;
1424 RootedShape lastRoot(cx, last);
1425 return replaceLastProperty(cx, base, proto, lastRoot);
1426 }
1428 /* static */ inline HashNumber
1429 StackBaseShape::hash(const StackBaseShape *base)
1430 {
1431 HashNumber hash = base->flags;
1432 hash = RotateLeft(hash, 4) ^ (uintptr_t(base->clasp) >> 3);
1433 hash = RotateLeft(hash, 4) ^ (uintptr_t(base->parent) >> 3);
1434 hash = RotateLeft(hash, 4) ^ (uintptr_t(base->metadata) >> 3);
1435 hash = RotateLeft(hash, 4) ^ uintptr_t(base->rawGetter);
1436 hash = RotateLeft(hash, 4) ^ uintptr_t(base->rawSetter);
1437 return hash;
1438 }
1440 /* static */ inline bool
1441 StackBaseShape::match(UnownedBaseShape *key, const StackBaseShape *lookup)
1442 {
1443 return key->flags == lookup->flags
1444 && key->clasp_ == lookup->clasp
1445 && key->parent == lookup->parent
1446 && key->metadata == lookup->metadata
1447 && key->rawGetter == lookup->rawGetter
1448 && key->rawSetter == lookup->rawSetter;
1449 }
1451 void
1452 StackBaseShape::trace(JSTracer *trc)
1453 {
1454 if (parent) {
1455 gc::MarkObjectRoot(trc, (JSObject**)&parent,
1456 "StackBaseShape parent");
1457 }
1458 if (metadata) {
1459 gc::MarkObjectRoot(trc, (JSObject**)&metadata,
1460 "StackBaseShape metadata");
1461 }
1462 if ((flags & BaseShape::HAS_GETTER_OBJECT) && rawGetter) {
1463 gc::MarkObjectRoot(trc, (JSObject**)&rawGetter,
1464 "StackBaseShape getter");
1465 }
1466 if ((flags & BaseShape::HAS_SETTER_OBJECT) && rawSetter) {
1467 gc::MarkObjectRoot(trc, (JSObject**)&rawSetter,
1468 "StackBaseShape setter");
1469 }
1470 }
1472 /* static */ UnownedBaseShape*
1473 BaseShape::getUnowned(ExclusiveContext *cx, StackBaseShape &base)
1474 {
1475 BaseShapeSet &table = cx->compartment()->baseShapes;
1477 if (!table.initialized() && !table.init())
1478 return nullptr;
1480 DependentAddPtr<BaseShapeSet> p(cx, table, &base);
1481 if (p)
1482 return *p;
1484 RootedGeneric<StackBaseShape*> root(cx, &base);
1486 BaseShape *nbase_ = js_NewGCBaseShape<CanGC>(cx);
1487 if (!nbase_)
1488 return nullptr;
1490 new (nbase_) BaseShape(*root);
1492 UnownedBaseShape *nbase = static_cast<UnownedBaseShape *>(nbase_);
1494 if (!p.add(cx, table, root, nbase))
1495 return nullptr;
1497 return nbase;
1498 }
1500 /* static */ UnownedBaseShape *
1501 BaseShape::lookupUnowned(ThreadSafeContext *cx, const StackBaseShape &base)
1502 {
1503 BaseShapeSet &table = cx->compartment_->baseShapes;
1505 if (!table.initialized())
1506 return nullptr;
1508 BaseShapeSet::Ptr p = table.readonlyThreadsafeLookup(&base);
1509 return *p;
1510 }
1512 void
1513 BaseShape::assertConsistency()
1514 {
1515 #ifdef DEBUG
1516 if (isOwned()) {
1517 UnownedBaseShape *unowned = baseUnowned();
1518 JS_ASSERT(hasGetterObject() == unowned->hasGetterObject());
1519 JS_ASSERT(hasSetterObject() == unowned->hasSetterObject());
1520 JS_ASSERT_IF(hasGetterObject(), getterObject() == unowned->getterObject());
1521 JS_ASSERT_IF(hasSetterObject(), setterObject() == unowned->setterObject());
1522 JS_ASSERT(getObjectParent() == unowned->getObjectParent());
1523 JS_ASSERT(getObjectMetadata() == unowned->getObjectMetadata());
1524 JS_ASSERT(getObjectFlags() == unowned->getObjectFlags());
1525 }
1526 #endif
1527 }
1529 void
1530 JSCompartment::sweepBaseShapeTable()
1531 {
1532 gcstats::AutoPhase ap(runtimeFromMainThread()->gcStats,
1533 gcstats::PHASE_SWEEP_TABLES_BASE_SHAPE);
1535 if (baseShapes.initialized()) {
1536 for (BaseShapeSet::Enum e(baseShapes); !e.empty(); e.popFront()) {
1537 UnownedBaseShape *base = e.front();
1538 if (IsBaseShapeAboutToBeFinalized(&base))
1539 e.removeFront();
1540 }
1541 }
1542 }
1544 void
1545 BaseShape::finalize(FreeOp *fop)
1546 {
1547 if (table_) {
1548 fop->delete_(table_);
1549 table_ = nullptr;
1550 }
1551 }
1553 inline
1554 InitialShapeEntry::InitialShapeEntry() : shape(nullptr), proto(nullptr)
1555 {
1556 }
1558 inline
1559 InitialShapeEntry::InitialShapeEntry(const ReadBarriered<Shape> &shape, TaggedProto proto)
1560 : shape(shape), proto(proto)
1561 {
1562 }
1564 inline InitialShapeEntry::Lookup
1565 InitialShapeEntry::getLookup() const
1566 {
1567 return Lookup(shape->getObjectClass(), proto, shape->getObjectParent(), shape->getObjectMetadata(),
1568 shape->numFixedSlots(), shape->getObjectFlags());
1569 }
1571 /* static */ inline HashNumber
1572 InitialShapeEntry::hash(const Lookup &lookup)
1573 {
1574 HashNumber hash = uintptr_t(lookup.clasp) >> 3;
1575 hash = RotateLeft(hash, 4) ^
1576 (uintptr_t(lookup.hashProto.toWord()) >> 3);
1577 hash = RotateLeft(hash, 4) ^
1578 (uintptr_t(lookup.hashParent) >> 3) ^
1579 (uintptr_t(lookup.hashMetadata) >> 3);
1580 return hash + lookup.nfixed;
1581 }
1583 /* static */ inline bool
1584 InitialShapeEntry::match(const InitialShapeEntry &key, const Lookup &lookup)
1585 {
1586 const Shape *shape = *key.shape.unsafeGet();
1587 return lookup.clasp == shape->getObjectClass()
1588 && lookup.matchProto.toWord() == key.proto.toWord()
1589 && lookup.matchParent == shape->getObjectParent()
1590 && lookup.matchMetadata == shape->getObjectMetadata()
1591 && lookup.nfixed == shape->numFixedSlots()
1592 && lookup.baseFlags == shape->getObjectFlags();
1593 }
1595 #ifdef JSGC_GENERATIONAL
1597 /*
1598 * This class is used to add a post barrier on the initialShapes set, as the key
1599 * is calculated based on several objects which may be moved by generational GC.
1600 */
1601 class InitialShapeSetRef : public BufferableRef
1602 {
1603 InitialShapeSet *set;
1604 const Class *clasp;
1605 TaggedProto proto;
1606 JSObject *parent;
1607 JSObject *metadata;
1608 size_t nfixed;
1609 uint32_t objectFlags;
1611 public:
1612 InitialShapeSetRef(InitialShapeSet *set,
1613 const Class *clasp,
1614 TaggedProto proto,
1615 JSObject *parent,
1616 JSObject *metadata,
1617 size_t nfixed,
1618 uint32_t objectFlags)
1619 : set(set),
1620 clasp(clasp),
1621 proto(proto),
1622 parent(parent),
1623 metadata(metadata),
1624 nfixed(nfixed),
1625 objectFlags(objectFlags)
1626 {}
1628 void mark(JSTracer *trc) {
1629 TaggedProto priorProto = proto;
1630 JSObject *priorParent = parent;
1631 JSObject *priorMetadata = metadata;
1632 if (proto.isObject())
1633 Mark(trc, reinterpret_cast<JSObject**>(&proto), "initialShapes set proto");
1634 if (parent)
1635 Mark(trc, &parent, "initialShapes set parent");
1636 if (metadata)
1637 Mark(trc, &metadata, "initialShapes set metadata");
1638 if (proto == priorProto && parent == priorParent && metadata == priorMetadata)
1639 return;
1641 /* Find the original entry, which must still be present. */
1642 InitialShapeEntry::Lookup lookup(clasp, priorProto,
1643 priorParent, parent,
1644 priorMetadata, metadata,
1645 nfixed, objectFlags);
1646 InitialShapeSet::Ptr p = set->lookup(lookup);
1647 JS_ASSERT(p);
1649 /* Update the entry's possibly-moved proto, and ensure lookup will still match. */
1650 InitialShapeEntry &entry = const_cast<InitialShapeEntry&>(*p);
1651 entry.proto = proto;
1652 lookup.matchProto = proto;
1654 /* Rekey the entry. */
1655 set->rekeyAs(lookup,
1656 InitialShapeEntry::Lookup(clasp, proto, parent, metadata, nfixed, objectFlags),
1657 *p);
1658 }
1659 };
1661 #ifdef JS_GC_ZEAL
1662 void
1663 JSCompartment::checkInitialShapesTableAfterMovingGC()
1664 {
1665 if (!initialShapes.initialized())
1666 return;
1668 /*
1669 * Assert that the postbarriers have worked and that nothing is left in
1670 * initialShapes that points into the nursery, and that the hash table
1671 * entries are discoverable.
1672 */
1673 JS::shadow::Runtime *rt = JS::shadow::Runtime::asShadowRuntime(runtimeFromMainThread());
1674 for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) {
1675 InitialShapeEntry entry = e.front();
1676 TaggedProto proto = entry.proto;
1677 Shape *shape = entry.shape.get();
1679 JS_ASSERT_IF(proto.isObject(), !IsInsideNursery(rt, proto.toObject()));
1680 JS_ASSERT(!IsInsideNursery(rt, shape->getObjectParent()));
1681 JS_ASSERT(!IsInsideNursery(rt, shape->getObjectMetadata()));
1683 InitialShapeEntry::Lookup lookup(shape->getObjectClass(),
1684 proto,
1685 shape->getObjectParent(),
1686 shape->getObjectMetadata(),
1687 shape->numFixedSlots(),
1688 shape->getObjectFlags());
1689 InitialShapeSet::Ptr ptr = initialShapes.lookup(lookup);
1690 JS_ASSERT(ptr.found() && &*ptr == &e.front());
1691 }
1692 }
1693 #endif
1695 #endif
1697 /* static */ Shape *
1698 EmptyShape::getInitialShape(ExclusiveContext *cx, const Class *clasp, TaggedProto proto,
1699 JSObject *parent, JSObject *metadata,
1700 size_t nfixed, uint32_t objectFlags)
1701 {
1702 JS_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject()));
1703 JS_ASSERT_IF(parent, cx->isInsideCurrentCompartment(parent));
1705 InitialShapeSet &table = cx->compartment()->initialShapes;
1707 if (!table.initialized() && !table.init())
1708 return nullptr;
1710 typedef InitialShapeEntry::Lookup Lookup;
1711 DependentAddPtr<InitialShapeSet>
1712 p(cx, table, Lookup(clasp, proto, parent, metadata, nfixed, objectFlags));
1713 if (p)
1714 return p->shape;
1716 Rooted<TaggedProto> protoRoot(cx, proto);
1717 RootedObject parentRoot(cx, parent);
1718 RootedObject metadataRoot(cx, metadata);
1720 StackBaseShape base(cx, clasp, parent, metadata, objectFlags);
1721 Rooted<UnownedBaseShape*> nbase(cx, BaseShape::getUnowned(cx, base));
1722 if (!nbase)
1723 return nullptr;
1725 Shape *shape = cx->compartment()->propertyTree.newShape(cx);
1726 if (!shape)
1727 return nullptr;
1728 new (shape) EmptyShape(nbase, nfixed);
1730 Lookup lookup(clasp, protoRoot, parentRoot, metadataRoot, nfixed, objectFlags);
1731 if (!p.add(cx, table, lookup, InitialShapeEntry(shape, protoRoot)))
1732 return nullptr;
1734 #ifdef JSGC_GENERATIONAL
1735 if (cx->hasNursery()) {
1736 if ((protoRoot.isObject() && cx->nursery().isInside(protoRoot.toObject())) ||
1737 cx->nursery().isInside(parentRoot.get()) ||
1738 cx->nursery().isInside(metadataRoot.get()))
1739 {
1740 InitialShapeSetRef ref(
1741 &table, clasp, protoRoot, parentRoot, metadataRoot, nfixed, objectFlags);
1742 cx->asJSContext()->runtime()->gcStoreBuffer.putGeneric(ref);
1743 }
1744 }
1745 #endif
1747 return shape;
1748 }
1750 /* static */ Shape *
1751 EmptyShape::getInitialShape(ExclusiveContext *cx, const Class *clasp, TaggedProto proto,
1752 JSObject *parent, JSObject *metadata,
1753 AllocKind kind, uint32_t objectFlags)
1754 {
1755 return getInitialShape(cx, clasp, proto, parent, metadata, GetGCKindSlots(kind, clasp), objectFlags);
1756 }
1758 void
1759 NewObjectCache::invalidateEntriesForShape(JSContext *cx, HandleShape shape, HandleObject proto)
1760 {
1761 const Class *clasp = shape->getObjectClass();
1763 gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
1764 if (CanBeFinalizedInBackground(kind, clasp))
1765 kind = GetBackgroundAllocKind(kind);
1767 Rooted<GlobalObject *> global(cx, &shape->getObjectParent()->global());
1768 Rooted<types::TypeObject *> type(cx, cx->getNewType(clasp, proto.get()));
1770 EntryIndex entry;
1771 if (lookupGlobal(clasp, global, kind, &entry))
1772 PodZero(&entries[entry]);
1773 if (!proto->is<GlobalObject>() && lookupProto(clasp, proto, kind, &entry))
1774 PodZero(&entries[entry]);
1775 if (lookupType(type, kind, &entry))
1776 PodZero(&entries[entry]);
1777 }
1779 /* static */ void
1780 EmptyShape::insertInitialShape(ExclusiveContext *cx, HandleShape shape, HandleObject proto)
1781 {
1782 InitialShapeEntry::Lookup lookup(shape->getObjectClass(), TaggedProto(proto),
1783 shape->getObjectParent(), shape->getObjectMetadata(),
1784 shape->numFixedSlots(), shape->getObjectFlags());
1786 InitialShapeSet::Ptr p = cx->compartment()->initialShapes.lookup(lookup);
1787 JS_ASSERT(p);
1789 InitialShapeEntry &entry = const_cast<InitialShapeEntry &>(*p);
1791 /* The new shape had better be rooted at the old one. */
1792 #ifdef DEBUG
1793 Shape *nshape = shape;
1794 while (!nshape->isEmptyShape())
1795 nshape = nshape->previous();
1796 JS_ASSERT(nshape == entry.shape);
1797 #endif
1799 entry.shape = shape.get();
1801 /*
1802 * This affects the shape that will be produced by the various NewObject
1803 * methods, so clear any cache entry referring to the old shape. This is
1804 * not required for correctness: the NewObject must always check for a
1805 * nativeEmpty() result and generate the appropriate properties if found.
1806 * Clearing the cache entry avoids this duplicate regeneration.
1807 *
1808 * Clearing is not necessary when this context is running off the main
1809 * thread, as it will not use the new object cache for allocations.
1810 */
1811 if (cx->isJSContext()) {
1812 JSContext *ncx = cx->asJSContext();
1813 ncx->runtime()->newObjectCache.invalidateEntriesForShape(ncx, shape, proto);
1814 }
1815 }
1817 void
1818 JSCompartment::sweepInitialShapeTable()
1819 {
1820 gcstats::AutoPhase ap(runtimeFromMainThread()->gcStats,
1821 gcstats::PHASE_SWEEP_TABLES_INITIAL_SHAPE);
1823 if (initialShapes.initialized()) {
1824 for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) {
1825 const InitialShapeEntry &entry = e.front();
1826 Shape *shape = entry.shape;
1827 JSObject *proto = entry.proto.raw();
1828 if (IsShapeAboutToBeFinalized(&shape) || (entry.proto.isObject() && IsObjectAboutToBeFinalized(&proto))) {
1829 e.removeFront();
1830 } else {
1831 #ifdef DEBUG
1832 DebugOnly<JSObject *> parent = shape->getObjectParent();
1833 JS_ASSERT(!parent || !IsObjectAboutToBeFinalized(&parent));
1834 JS_ASSERT(parent == shape->getObjectParent());
1835 #endif
1836 if (shape != entry.shape || proto != entry.proto.raw()) {
1837 InitialShapeEntry newKey(shape, proto);
1838 e.rekeyFront(newKey.getLookup(), newKey);
1839 }
1840 }
1841 }
1842 }
1843 }
1845 void
1846 AutoRooterGetterSetter::Inner::trace(JSTracer *trc)
1847 {
1848 if ((attrs & JSPROP_GETTER) && *pgetter)
1849 gc::MarkObjectRoot(trc, (JSObject**) pgetter, "AutoRooterGetterSetter getter");
1850 if ((attrs & JSPROP_SETTER) && *psetter)
1851 gc::MarkObjectRoot(trc, (JSObject**) psetter, "AutoRooterGetterSetter setter");
1852 }