1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sw=4 et tw=78:
4 * ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
17 * The Original Code is Mozilla Communicator client code, released
20 * The Initial Developer of the Original Code is
21 * Netscape Communications Corporation.
22 * Portions created by the Initial Developer are Copyright (C) 1998
23 * the Initial Developer. All Rights Reserved.
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
58 #include "jsfun.h" /* for JS_ARGS_LENGTH_MAX */
66 #include "jsdbgapiinlines.h"
67 #include "jsobjinlines.h"
68 #include "jsscopeinlines.h"
71 using namespace js::gc;
74 js_GenerateShape(JSRuntime *rt)
78 shape = JS_ATOMIC_INCREMENT(&rt->shapeGen);
79 JS_ASSERT(shape != 0);
80 if (shape >= SHAPE_OVERFLOW_BIT) {
82 * FIXME bug 440834: The shape id space has overflowed. Currently we
83 * cope badly with this and schedule the GC on the every call. But
84 * first we make sure that increments from other threads would not
85 * have a chance to wrap around shapeGen to zero.
87 rt->shapeGen = SHAPE_OVERFLOW_BIT;
88 shape = SHAPE_OVERFLOW_BIT;
91 AutoLockGC lockIf(rt);
99 js_GenerateShape(JSContext *cx)
101 return js_GenerateShape(cx->runtime);
105 JSObject::ensureClassReservedSlotsForEmptyObject(JSContext *cx)
107 JS_ASSERT(nativeEmpty());
110 * Subtle rule: objects that call JSObject::ensureInstanceReservedSlots
113 * (a) never escape anywhere an ad-hoc property could be set on them; or
115 * (b) protect their instance-reserved slots with shapes, at least a custom
116 * empty shape with the right slotSpan member.
118 * Block objects are the only objects that fall into category (a). While
119 * Call objects cannot escape, they can grow ad-hoc properties via eval
120 * of a var declaration, or due to a function statement being evaluated,
121 * but they have slots mapped by compiler-created shapes, and thus (b) no
122 * problem predicting first ad-hoc property slot. Bound Function objects
123 * have a custom empty shape.
125 * (Note that Block, Call, and bound Function objects are the only native
126 * class objects that are allowed to call ensureInstanceReservedSlots.)
128 uint32 nfixed = JSSLOT_FREE(getClass());
129 if (nfixed > numSlots() && !allocSlots(cx, nfixed))
135 #define PROPERTY_TABLE_NBYTES(n) ((n) * sizeof(Shape *))
138 JS_FRIEND_DATA(JSScopeStats) js_scope_stats = {0};
140 # define METER(x) JS_ATOMIC_INCREMENT(&js_scope_stats.x)
142 # define METER(x) ((void) 0)
146 PropertyTable::init(JSRuntime *rt, Shape *lastProp)
149 * Either we're creating a table for a large scope that was populated
150 * via property cache hit logic under JSOP_INITPROP, JSOP_SETNAME, or
151 * JSOP_SETPROP; or else calloc failed at least once already. In any
152 * event, let's try to grow, overallocating to hold at least twice the
153 * current population.
155 uint32 sizeLog2 = JS_CeilingLog2(2 * entryCount);
156 if (sizeLog2 < MIN_SIZE_LOG2)
157 sizeLog2 = MIN_SIZE_LOG2;
160 * Use rt->calloc for memory accounting and overpressure handling
161 * without OOM reporting. See PropertyTable::change.
163 entries = (Shape **) rt->calloc(JS_BIT(sizeLog2) * sizeof(Shape *));
165 METER(tableAllocFails);
169 hashShift = JS_DHASH_BITS - sizeLog2;
170 for (Shape::Range r = lastProp->all(); !r.empty(); r.popFront()) {
171 const Shape &shape = r.front();
174 Shape **spp = search(shape.id, true);
177 * Beware duplicate args and arg vs. var conflicts: the youngest shape
178 * (nearest to lastProp) must win. See bug 600067.
180 if (!SHAPE_FETCH(spp))
181 SHAPE_STORE_PRESERVING_COLLISION(spp, &shape);
187 Shape::hashify(JSRuntime *rt)
189 JS_ASSERT(!hasTable());
190 void* mem = rt->malloc(sizeof(PropertyTable));
193 setTable(new(mem) PropertyTable(entryCount()));
194 return getTable()->init(rt, this);
199 # define LIVE_SCOPE_METER(cx,expr) JS_LOCK_RUNTIME_VOID(cx->runtime,expr)
201 # define LIVE_SCOPE_METER(cx,expr) /* nothing */
205 InitField(JSCompartment *comp, EmptyShape *JSCompartment:: *field, Class *clasp)
207 if (EmptyShape *emptyShape = EmptyShape::create(comp, clasp)) {
208 comp->*field = emptyShape;
216 Shape::initEmptyShapes(JSCompartment *comp)
219 * NewArguments allocates dslots to have enough room for the argc of the
220 * particular arguments object being created.
221 * never mutated, it's safe to pretend to have all the slots possible.
223 * Note how the fast paths in jsinterp.cpp for JSOP_LENGTH and JSOP_GETELEM
224 * bypass resolution of scope properties for length and element indices on
225 * arguments objects. This helps ensure that any arguments object needing
226 * its own mutable scope (with unique shape) is a rare event.
228 if (!InitField(comp, &JSCompartment::emptyArgumentsShape, &js_ArgumentsClass))
231 if (!InitField(comp, &JSCompartment::emptyBlockShape, &js_BlockClass))
235 * Initialize the shared scope for all empty Call objects so gets for args
236 * and vars do not force the creation of a mutable scope for the particular
237 * call object being accessed.
239 if (!InitField(comp, &JSCompartment::emptyCallShape, &js_CallClass))
242 /* A DeclEnv object holds the name binding for a named function expression. */
243 if (!InitField(comp, &JSCompartment::emptyDeclEnvShape, &js_DeclEnvClass))
246 /* Non-escaping native enumerator objects share this empty scope. */
247 if (!InitField(comp, &JSCompartment::emptyEnumeratorShape, &js_IteratorClass))
250 /* Same drill for With objects. */
251 if (!InitField(comp, &JSCompartment::emptyWithShape, &js_WithClass))
259 Shape::finishEmptyShapes(JSCompartment *comp)
261 comp->emptyArgumentsShape = NULL;
262 comp->emptyBlockShape = NULL;
263 comp->emptyCallShape = NULL;
264 comp->emptyDeclEnvShape = NULL;
265 comp->emptyEnumeratorShape = NULL;
266 comp->emptyWithShape = NULL;
269 JS_STATIC_ASSERT(sizeof(JSHashNumber) == 4);
270 JS_STATIC_ASSERT(sizeof(jsid) == JS_BYTES_PER_WORD);
272 #if JS_BYTES_PER_WORD == 4
273 # define HASH_ID(id) ((JSHashNumber)(JSID_BITS(id)))
274 #elif JS_BYTES_PER_WORD == 8
275 # define HASH_ID(id) ((JSHashNumber)(JSID_BITS(id)) ^ (JSHashNumber)((JSID_BITS(id)) >> 32))
277 # error "Unsupported configuration"
281 * Double hashing needs the second hash code to be relatively prime to table
282 * size, so we simply make hash2 odd. The inputs to multiplicative hash are
283 * the golden ratio, expressed as a fixed-point 32 bit fraction, and the id
286 #define HASH0(id) (HASH_ID(id) * JS_GOLDEN_RATIO)
287 #define HASH1(hash0,shift) ((hash0) >> (shift))
288 #define HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1)
291 PropertyTable::search(jsid id, bool adding)
293 JSHashNumber hash0, hash1, hash2;
295 Shape *stored, *shape, **spp, **firstRemoved;
299 JS_ASSERT(!JSID_IS_VOID(id));
301 /* Compute the primary hash address. */
304 hash1 = HASH1(hash0, hashShift);
305 spp = entries + hash1;
307 /* Miss: return space for a new entry. */
309 if (SHAPE_IS_FREE(stored)) {
315 /* Hit: return entry. */
316 shape = SHAPE_CLEAR_COLLISION(stored);
317 if (shape && shape->id == id) {
323 /* Collision: double hash. */
324 sizeLog2 = JS_DHASH_BITS - hashShift;
325 hash2 = HASH2(hash0, sizeLog2, hashShift);
326 sizeMask = JS_BITMASK(sizeLog2);
329 jsuword collision_flag = SHAPE_COLLISION;
332 /* Save the first removed entry pointer so we can recycle it if adding. */
333 if (SHAPE_IS_REMOVED(stored)) {
337 if (adding && !SHAPE_HAD_COLLISION(stored))
338 SHAPE_FLAG_COLLISION(spp, shape);
340 collision_flag &= jsuword(*spp) & SHAPE_COLLISION;
348 spp = entries + hash1;
351 if (SHAPE_IS_FREE(stored)) {
354 return (adding && firstRemoved) ? firstRemoved : spp;
357 shape = SHAPE_CLEAR_COLLISION(stored);
358 if (shape && shape->id == id) {
361 JS_ASSERT(collision_flag);
365 if (SHAPE_IS_REMOVED(stored)) {
369 if (adding && !SHAPE_HAD_COLLISION(stored))
370 SHAPE_FLAG_COLLISION(spp, shape);
372 collision_flag &= jsuword(*spp) & SHAPE_COLLISION;
382 PropertyTable::change(int log2Delta, JSContext *cx)
384 int oldlog2, newlog2;
385 uint32 oldsize, newsize, nbytes;
386 Shape **newTable, **oldTable, **spp, **oldspp, *shape;
391 * Grow, shrink, or compress by changing this->entries. Here, we prefer
392 * cx->runtime->calloc to js_calloc, which on OOM waits for a background
393 * thread to finish sweeping and retry, if appropriate. Avoid cx->calloc
394 * so our caller can be in charge of whether to JS_ReportOutOfMemory.
396 oldlog2 = JS_DHASH_BITS - hashShift;
397 newlog2 = oldlog2 + log2Delta;
398 oldsize = JS_BIT(oldlog2);
399 newsize = JS_BIT(newlog2);
400 nbytes = PROPERTY_TABLE_NBYTES(newsize);
401 newTable = (Shape **) cx->runtime->calloc(nbytes);
403 METER(tableAllocFails);
407 /* Now that we have newTable allocated, update members. */
408 hashShift = JS_DHASH_BITS - newlog2;
413 /* Copy only live entries, leaving removed and free ones behind. */
414 for (oldspp = oldTable; oldsize != 0; oldspp++) {
415 shape = SHAPE_FETCH(oldspp);
418 METER(changeSearches);
419 spp = search(shape->id, true);
420 JS_ASSERT(SHAPE_IS_FREE(*spp));
427 * Finally, free the old entries storage. Note that cx->runtime->free just
428 * calls js_free. Use js_free here to match PropertyTable::~PropertyTable,
429 * which cannot have a cx or rt parameter.
436 PropertyTable::grow(JSContext *cx)
438 JS_ASSERT(needsToGrow());
440 uint32 size = capacity();
441 int delta = removedCount < size >> 2;
447 if (!change(delta, cx) && entryCount + removedCount == size - 1) {
448 JS_ReportOutOfMemory(cx);
455 Shape::getChild(JSContext *cx, const js::Shape &child, Shape **listp)
457 JS_ASSERT(!JSID_IS_VOID(child.id));
458 JS_ASSERT(!child.inDictionary());
460 if (inDictionary()) {
461 Shape *oldShape = *listp;
462 PropertyTable *table = (oldShape && oldShape->hasTable()) ? oldShape->getTable() : NULL;
465 * Attempt to grow table if needed before extending *listp, rather than
466 * risking OOM under table->grow after newDictionaryShape succeeds, and
467 * then have to fix up *listp.
469 if (table && table->needsToGrow() && !table->grow(cx))
472 if (newDictionaryShape(cx, child, listp)) {
473 Shape *newShape = *listp;
475 JS_ASSERT(oldShape == newShape->parent);
477 /* Add newShape to the property table. */
479 Shape **spp = table->search(newShape->id, true);
482 * Beware duplicate formal parameters, allowed by ECMA-262 in
483 * non-strict mode. Otherwise we know that Bindings::add (our
484 * caller) won't pass an id already in the table to us. In the
485 * case of duplicate formals, the last one wins, so while we
486 * must not overcount entries, we must store newShape.
488 if (!SHAPE_FETCH(spp))
490 SHAPE_STORE_PRESERVING_COLLISION(spp, newShape);
492 /* Hand the table off from oldShape to newShape. */
493 oldShape->setTable(NULL);
494 newShape->setTable(table);
496 if (!newShape->hasTable())
497 newShape->hashify(cx->runtime);
505 if ((*listp)->entryCount() >= PropertyTree::MAX_HEIGHT) {
506 Shape *dprop = Shape::newDictionaryList(cx, listp);
509 return dprop->getChild(cx, child, listp);
512 Shape *shape = JS_PROPERTY_TREE(cx).getChild(cx, this, child);
514 JS_ASSERT(shape->parent == this);
515 JS_ASSERT(this == *listp);
522 * Get or create a property-tree or dictionary child property of parent, which
523 * must be lastProp if inDictionaryMode(), else parent must be one of lastProp
524 * or lastProp->parent.
527 JSObject::getChildProperty(JSContext *cx, Shape *parent, Shape &child)
529 JS_ASSERT(!JSID_IS_VOID(child.id));
530 JS_ASSERT(!child.inDictionary());
533 * Aliases share another property's slot, passed in the |slot| parameter.
534 * Shared properties have no slot. Unshared properties that do not alias
535 * another property's slot allocate a slot here, but may lose it due to a
536 * JS_ClearScope call.
538 if (!child.isAlias()) {
539 if (child.attrs & JSPROP_SHARED) {
540 child.slot = SHAPE_INVALID_SLOT;
543 * We may have set slot from a nearly-matching shape, above. If so,
544 * we're overwriting that nearly-matching shape, so we can reuse
545 * its slot -- we don't need to allocate a new one. Similarly, we
546 * use a specific slot if provided by the caller.
548 if (child.slot == SHAPE_INVALID_SLOT && !allocSlot(cx, &child.slot))
555 if (inDictionaryMode()) {
556 JS_ASSERT(parent == lastProp);
557 if (parent->frozen()) {
558 parent = Shape::newDictionaryList(cx, &lastProp);
561 JS_ASSERT(!parent->frozen());
563 shape = Shape::newDictionaryShape(cx, child, &lastProp);
567 shape = JS_PROPERTY_TREE(cx).getChild(cx, parent, child);
570 JS_ASSERT(shape->parent == parent);
571 JS_ASSERT_IF(parent != lastProp, parent == lastProp->parent);
572 setLastProperty(shape);
581 Shape::newDictionaryShape(JSContext *cx, const Shape &child, Shape **listp)
583 Shape *dprop = JS_PROPERTY_TREE(cx).newShape(cx);
587 new (dprop) Shape(child.id, child.rawGetter, child.rawSetter, child.slot, child.attrs,
588 (child.flags & ~FROZEN) | IN_DICTIONARY, child.shortid,
589 js_GenerateShape(cx), child.slotSpan);
592 dprop->insertIntoDictionary(listp);
594 JS_COMPARTMENT_METER(cx->compartment->liveDictModeNodes++);
599 Shape::newDictionaryList(JSContext *cx, Shape **listp)
601 Shape *shape = *listp;
604 Shape **childp = listp;
608 JS_ASSERT_IF(!shape->frozen(), !shape->inDictionary());
610 Shape *dprop = Shape::newDictionaryShape(cx, *shape, childp);
617 JS_ASSERT(!dprop->hasTable());
618 childp = &dprop->parent;
619 shape = shape->parent;
623 JS_ASSERT(list->inDictionary());
624 list->hashify(cx->runtime);
629 JSObject::toDictionaryMode(JSContext *cx)
631 JS_ASSERT(!inDictionaryMode());
632 if (!Shape::newDictionaryList(cx, &lastProp))
640 * Normalize stub getter and setter values for faster is-stub testing in the
641 * SHAPE_CALL_[GS]ETTER macros.
644 NormalizeGetterAndSetter(JSContext *cx, JSObject *obj,
645 jsid id, uintN attrs, uintN flags,
647 StrictPropertyOp &setter)
649 if (setter == StrictPropertyStub) {
650 JS_ASSERT(!(attrs & JSPROP_SETTER));
653 if (flags & Shape::METHOD) {
654 /* Here, getter is the method, a function object reference. */
656 JS_ASSERT(!setter || setter == js_watch_set);
657 JS_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
659 if (getter == PropertyStub) {
660 JS_ASSERT(!(attrs & JSPROP_GETTER));
669 # define CHECK_SHAPE_CONSISTENCY(obj) obj->checkShapeConsistency()
672 JSObject::checkShapeConsistency()
674 static int throttle = -1;
676 if (const char *var = getenv("JS_CHECK_SHAPE_THROTTLE"))
677 throttle = atoi(var);
684 JS_ASSERT(isNative());
686 JS_ASSERT(objShape != lastProp->shape);
688 JS_ASSERT(objShape == lastProp->shape);
690 Shape *shape = lastProp;
693 if (inDictionaryMode()) {
694 if (shape->hasTable()) {
695 PropertyTable *table = shape->getTable();
696 for (uint32 fslot = table->freelist; fslot != SHAPE_INVALID_SLOT;
697 fslot = getSlotRef(fslot).toPrivateUint32()) {
698 JS_ASSERT(fslot < shape->slotSpan);
701 for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) {
702 JS_ASSERT_IF(shape != lastProp, !shape->hasTable());
704 Shape **spp = table->search(shape->id, false);
705 JS_ASSERT(SHAPE_FETCH(spp) == shape);
708 shape = shape->parent;
709 for (int n = throttle; --n >= 0 && shape; shape = shape->parent)
710 JS_ASSERT(!shape->hasTable());
714 for (int n = throttle; --n >= 0 && shape; shape = shape->parent) {
715 JS_ASSERT_IF(shape->slot != SHAPE_INVALID_SLOT, shape->slot < shape->slotSpan);
717 JS_ASSERT(shape == lastProp);
718 JS_ASSERT(shape->listp == &lastProp);
720 JS_ASSERT(shape->listp == &prev->parent);
721 JS_ASSERT(prev->slotSpan >= shape->slotSpan);
726 for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) {
727 if (shape->hasTable()) {
728 PropertyTable *table = shape->getTable();
729 JS_ASSERT(shape->parent);
730 for (Shape::Range r(shape); !r.empty(); r.popFront()) {
731 Shape **spp = table->search(r.front().id, false);
732 JS_ASSERT(SHAPE_FETCH(spp) == &r.front());
736 JS_ASSERT(prev->slotSpan >= shape->slotSpan);
737 shape->kids.checkConsistency(prev);
744 # define CHECK_SHAPE_CONSISTENCY(obj) ((void)0)
748 JSObject::addProperty(JSContext *cx, jsid id,
749 PropertyOp getter, StrictPropertyOp setter,
750 uint32 slot, uintN attrs,
751 uintN flags, intN shortid)
753 JS_ASSERT(!JSID_IS_VOID(id));
755 if (!isExtensible()) {
756 reportNotExtensible(cx);
760 NormalizeGetterAndSetter(cx, this, id, attrs, flags, getter, setter);
762 /* Search for id with adding = true in order to claim its entry. */
763 Shape **spp = nativeSearch(id, true);
764 JS_ASSERT(!SHAPE_FETCH(spp));
765 const Shape *shape = addPropertyInternal(cx, id, getter, setter, slot, attrs,
766 flags, shortid, spp);
770 /* Update any watchpoints referring to this property. */
771 shape = js_UpdateWatchpointsForShape(cx, this, shape);
773 METER(wrapWatchFails);
779 JSObject::addPropertyInternal(JSContext *cx, jsid id,
780 PropertyOp getter, StrictPropertyOp setter,
781 uint32 slot, uintN attrs,
782 uintN flags, intN shortid,
785 JS_ASSERT_IF(inDictionaryMode(), !lastProp->frozen());
787 PropertyTable *table = NULL;
788 if (!inDictionaryMode()) {
789 if (lastProp->entryCount() >= PropertyTree::MAX_HEIGHT) {
790 if (!toDictionaryMode(cx))
792 spp = nativeSearch(id, true);
793 table = lastProp->getTable();
795 } else if (lastProp->hasTable()) {
796 table = lastProp->getTable();
797 if (table->needsToGrow()) {
798 if (!table->grow(cx))
802 METER(changeSearches);
803 spp = table->search(id, true);
804 JS_ASSERT(!SHAPE_FETCH(spp));
808 /* Find or create a property tree node labeled by our arguments. */
811 Shape child(id, getter, setter, slot, attrs, flags, shortid);
812 shape = getChildProperty(cx, lastProp, child);
816 JS_ASSERT(shape == lastProp);
819 /* Store the tree node pointer in the table entry for id. */
820 SHAPE_STORE_PRESERVING_COLLISION(spp, shape);
823 /* Pass the table along to the new lastProp, namely shape. */
824 JS_ASSERT(shape->parent->getTable() == table);
825 shape->parent->setTable(NULL);
826 shape->setTable(table);
829 LIVE_SCOPE_METER(cx, ++cx->runtime->liveObjectProps);
831 CHECK_SHAPE_CONSISTENCY(this);
836 CHECK_SHAPE_CONSISTENCY(this);
842 * Check and adjust the new attributes for the shape to make sure that our
843 * slot access optimizations are sound. It is responsibility of the callers to
844 * enforce all restrictions from ECMA-262 v5 8.12.9 [[DefineOwnProperty]].
847 CheckCanChangeAttrs(JSContext *cx, JSObject *obj, const Shape *shape, uintN *attrsp)
849 if (shape->configurable())
852 /* A permanent property must stay permanent. */
853 *attrsp |= JSPROP_PERMANENT;
855 /* Reject attempts to remove a slot from the permanent data property. */
856 if (shape->isDataDescriptor() && shape->hasSlot() &&
857 (*attrsp & (JSPROP_GETTER | JSPROP_SETTER | JSPROP_SHARED))) {
858 obj->reportNotConfigurable(cx, shape->id);
866 JSObject::putProperty(JSContext *cx, jsid id,
867 PropertyOp getter, StrictPropertyOp setter,
868 uint32 slot, uintN attrs,
869 uintN flags, intN shortid)
871 JS_ASSERT(!JSID_IS_VOID(id));
874 * Horrid non-strict eval, debuggers, and |default xml namespace ...| may
875 * extend Call objects.
877 if (lastProp->frozen()) {
878 if (!Shape::newDictionaryList(cx, &lastProp))
880 JS_ASSERT(!lastProp->frozen());
883 NormalizeGetterAndSetter(cx, this, id, attrs, flags, getter, setter);
885 /* Search for id in order to claim its entry if table has been allocated. */
886 Shape **spp = nativeSearch(id, true);
887 Shape *shape = SHAPE_FETCH(spp);
890 * You can't add properties to a non-extensible object, but you can change
891 * attributes of properties in such objects.
893 if (!isExtensible()) {
894 reportNotExtensible(cx);
898 const Shape *newShape =
899 addPropertyInternal(cx, id, getter, setter, slot, attrs, flags, shortid, spp);
902 newShape = js_UpdateWatchpointsForShape(cx, this, newShape);
904 METER(wrapWatchFails);
908 /* Property exists: search must have returned a valid *spp. */
909 JS_ASSERT(!SHAPE_IS_REMOVED(*spp));
911 if (!CheckCanChangeAttrs(cx, this, shape, &attrs))
915 * If the caller wants to allocate a slot, but doesn't care which slot,
916 * copy the existing shape's slot into slot so we can match shape, if all
917 * other members match.
919 bool hadSlot = !shape->isAlias() && shape->hasSlot();
920 uint32 oldSlot = shape->slot;
921 if (!(attrs & JSPROP_SHARED) && slot == SHAPE_INVALID_SLOT && hadSlot)
925 * Now that we've possibly preserved slot, check whether all members match.
926 * If so, this is a redundant "put" and we can return without more work.
928 if (shape->matchesParamsAfterId(getter, setter, slot, attrs, flags, shortid)) {
929 METER(redundantPuts);
934 * Overwriting a non-last property requires switching to dictionary mode.
935 * The shape tree is shared immutable, and we can't removeProperty and then
936 * addPropertyInternal because a failure under add would lose data.
938 if (shape != lastProp && !inDictionaryMode()) {
939 if (!toDictionaryMode(cx))
941 spp = nativeSearch(shape->id);
942 shape = SHAPE_FETCH(spp);
946 * Now that we have passed the lastProp->frozen() check at the top of this
947 * method, and the non-last-property conditioning just above, we are ready
950 * Optimize the case of a non-frozen dictionary-mode object based on the
951 * property that dictionaries exclusively own their mutable shape structs,
952 * each of which has a unique shape number (not shared via a shape tree).
954 * This is more than an optimization: it is required to preserve for-in
955 * enumeration order (see bug 601399).
957 if (inDictionaryMode()) {
958 /* FIXME bug 593129 -- slot allocation and JSObject *this must move out of here! */
959 if (slot == SHAPE_INVALID_SLOT && !(attrs & JSPROP_SHARED) && !(flags & Shape::ALIAS)) {
960 if (!allocSlot(cx, &slot))
965 if (slot != SHAPE_INVALID_SLOT && slot >= shape->slotSpan) {
966 shape->slotSpan = slot + 1;
968 for (Shape *temp = lastProp; temp != shape; temp = temp->parent) {
969 if (temp->slotSpan <= slot)
970 temp->slotSpan = slot + 1;
974 shape->rawGetter = getter;
975 shape->rawSetter = setter;
976 shape->attrs = uint8(attrs);
977 shape->flags = flags | Shape::IN_DICTIONARY;
978 shape->shortid = int16(shortid);
981 * We are done updating shape and lastProp. Now we may need to update
982 * flags and we will need to update objShape, which is no longer "own".
983 * In the last non-dictionary property case in the else clause just
984 * below, getChildProperty handles this for us. First update flags.
989 * We have just mutated shape in place, but nothing caches it based on
990 * shape->shape unless shape is lastProp and !hasOwnShape()). Therefore
991 * we regenerate only lastProp->shape. We will clearOwnShape(), which
992 * sets objShape to lastProp->shape.
994 lastProp->shape = js_GenerateShape(cx);
998 * Updating lastProp in a non-dictionary-mode object. Such objects
999 * share their shapes via a tree rooted at a prototype emptyShape, or
1000 * perhaps a well-known compartment-wide singleton emptyShape.
1002 * If any shape in the tree has a property hashtable, it is shared and
1003 * immutable too, therefore we must not update *spp.
1005 JS_ASSERT(shape == lastProp);
1006 removeLastProperty();
1008 /* Find or create a property tree node labeled by our arguments. */
1009 Shape child(id, getter, setter, slot, attrs, flags, shortid);
1011 Shape *newShape = getChildProperty(cx, lastProp, child);
1013 setLastProperty(shape);
1014 CHECK_SHAPE_CONSISTENCY(this);
1023 * Can't fail now, so free the previous incarnation's slot if the new shape
1024 * has no slot. But we do not need to free oldSlot (and must not, as trying
1025 * to will botch an assertion in JSObject::freeSlot) if the new lastProp
1026 * (shape here) has a slotSpan that does not cover it.
1028 if (hadSlot && !shape->hasSlot()) {
1029 if (oldSlot < shape->slotSpan)
1030 freeSlot(cx, oldSlot);
1033 getSlotRef(oldSlot).setUndefined();
1035 JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals);
1038 CHECK_SHAPE_CONSISTENCY(this);
1041 const Shape *newShape = js_UpdateWatchpointsForShape(cx, this, shape);
1043 METER(wrapWatchFails);
1048 JSObject::changeProperty(JSContext *cx, const Shape *shape, uintN attrs, uintN mask,
1049 PropertyOp getter, StrictPropertyOp setter)
1051 JS_ASSERT_IF(inDictionaryMode(), !lastProp->frozen());
1052 JS_ASSERT(!JSID_IS_VOID(shape->id));
1053 JS_ASSERT(nativeContains(*shape));
1055 attrs |= shape->attrs & mask;
1057 /* Allow only shared (slotless) => unshared (slotful) transition. */
1058 JS_ASSERT(!((attrs ^ shape->attrs) & JSPROP_SHARED) ||
1059 !(attrs & JSPROP_SHARED));
1061 /* Don't allow method properties to be changed to have a getter. */
1062 JS_ASSERT_IF(getter != shape->rawGetter, !shape->isMethod());
1064 if (getter == PropertyStub)
1066 if (setter == StrictPropertyStub)
1069 if (!CheckCanChangeAttrs(cx, this, shape, &attrs))
1072 if (shape->attrs == attrs && shape->getter() == getter && shape->setter() == setter)
1075 const Shape *newShape;
1078 * Dictionary-mode objects exclusively own their mutable shape structs, so
1079 * we simply modify in place.
1081 if (inDictionaryMode()) {
1082 /* FIXME bug 593129 -- slot allocation and JSObject *this must move out of here! */
1083 uint32 slot = shape->slot;
1084 if (slot == SHAPE_INVALID_SLOT && !(attrs & JSPROP_SHARED) && !(flags & Shape::ALIAS)) {
1085 if (!allocSlot(cx, &slot))
1089 Shape *mutableShape = const_cast<Shape *>(shape);
1090 mutableShape->slot = slot;
1091 if (slot != SHAPE_INVALID_SLOT && slot >= shape->slotSpan) {
1092 mutableShape->slotSpan = slot + 1;
1094 for (Shape *temp = lastProp; temp != shape; temp = temp->parent) {
1095 if (temp->slotSpan <= slot)
1096 temp->slotSpan = slot + 1;
1100 mutableShape->rawGetter = getter;
1101 mutableShape->rawSetter = setter;
1102 mutableShape->attrs = uint8(attrs);
1106 /* See the corresponding code in putProperty. */
1107 lastProp->shape = js_GenerateShape(cx);
1110 shape = js_UpdateWatchpointsForShape(cx, this, shape);
1112 METER(wrapWatchFails);
1115 JS_ASSERT(shape == mutableShape);
1116 newShape = mutableShape;
1117 } else if (shape == lastProp) {
1118 Shape child(shape->id, getter, setter, shape->slot, attrs, shape->flags, shape->shortid);
1120 newShape = getChildProperty(cx, shape->parent, child);
1123 JS_ASSERT(newShape == lastProp);
1124 if (newShape->hasTable()) {
1125 Shape **spp = nativeSearch(shape->id);
1126 JS_ASSERT(SHAPE_FETCH(spp) == newShape);
1132 * Let JSObject::putProperty handle this |overwriting| case, including
1133 * the conservation of shape->slot (if it's valid). We must not call
1134 * removeProperty because it will free an allocated shape->slot, and
1135 * putProperty won't re-allocate it.
1137 Shape child(shape->id, getter, setter, shape->slot, attrs, shape->flags, shape->shortid);
1138 newShape = putProperty(cx, child.id, child.rawGetter, child.rawSetter, child.slot,
1139 child.attrs, child.flags, child.shortid);
1147 CHECK_SHAPE_CONSISTENCY(this);
1157 JSObject::removeProperty(JSContext *cx, jsid id)
1159 Shape **spp = nativeSearch(id);
1160 Shape *shape = SHAPE_FETCH(spp);
1162 METER(uselessRemoves);
1166 /* First, if shape is unshared and not has a slot, free its slot number. */
1167 bool addedToFreelist = false;
1168 bool hadSlot = !shape->isAlias() && shape->hasSlot();
1170 addedToFreelist = freeSlot(cx, shape->slot);
1171 JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals);
1175 /* If shape is not the last property added, switch to dictionary mode. */
1176 if (shape != lastProp && !inDictionaryMode()) {
1177 if (!toDictionaryMode(cx))
1179 spp = nativeSearch(shape->id);
1180 shape = SHAPE_FETCH(spp);
1184 * A dictionary-mode object owns mutable, unique shapes on a non-circular
1185 * doubly linked list, optionally hashed by lastProp->table. So we can edit
1186 * the list and hash in place.
1188 if (inDictionaryMode()) {
1189 PropertyTable *table = lastProp->hasTable() ? lastProp->getTable() : NULL;
1191 if (SHAPE_HAD_COLLISION(*spp)) {
1193 *spp = SHAPE_REMOVED;
1194 ++table->removedCount;
1195 --table->entryCount;
1200 --table->entryCount;
1204 * Check the consistency of the table but limit the number of
1205 * checks not to alter significantly the complexity of the
1206 * delete in debug builds, see bug 534493.
1208 const Shape *aprop = lastProp;
1209 for (int n = 50; --n >= 0 && aprop->parent; aprop = aprop->parent)
1210 JS_ASSERT_IF(aprop != shape, nativeContains(*aprop));
1216 * Remove shape from its non-circular doubly linked list, setting this
1217 * object's OWN_SHAPE flag so the updateShape(cx) further below will
1218 * generate a fresh shape id for this object, distinct from the id of
1219 * any shape in the list. We need a fresh shape for all deletions, even
1220 * of lastProp. Otherwise, a shape number could replay and caches might
1221 * return get deleted DictionaryShapes! See bug 595365.
1225 Shape *oldLastProp = lastProp;
1226 shape->removeFromDictionary(this);
1228 if (shape == oldLastProp) {
1229 JS_ASSERT(shape->getTable() == table);
1230 JS_ASSERT(shape->parent == lastProp);
1231 JS_ASSERT(shape->slotSpan >= lastProp->slotSpan);
1232 JS_ASSERT_IF(hadSlot, shape->slot + 1 <= shape->slotSpan);
1235 * Maintain slot freelist consistency. The only constraint we
1236 * have is that slot numbers on the freelist are less than
1237 * lastProp->slotSpan. Thus, if the freelist is non-empty,
1238 * then lastProp->slotSpan may not decrease.
1240 if (table->freelist != SHAPE_INVALID_SLOT) {
1241 lastProp->slotSpan = shape->slotSpan;
1243 /* Add the slot to the freelist if it wasn't added in freeSlot. */
1244 if (hadSlot && !addedToFreelist) {
1245 getSlotRef(shape->slot).setPrivateUint32(table->freelist);
1246 table->freelist = shape->slot;
1251 /* Hand off table from old to new lastProp. */
1252 oldLastProp->setTable(NULL);
1253 lastProp->setTable(table);
1257 * Non-dictionary-mode property tables are shared immutables, so all we
1258 * need do is retract lastProp and we'll either get or else lazily make
1259 * via a later hashify the exact table for the new property lineage.
1261 JS_ASSERT(shape == lastProp);
1262 removeLastProperty();
1265 * Revert to fixed slots if this was the first dynamically allocated slot,
1266 * preserving invariant that objects with the same shape use the fixed
1267 * slots in the same way.
1269 size_t fixed = numFixedSlots();
1270 if (shape->slot == fixed) {
1271 JS_ASSERT_IF(!lastProp->isEmptyShape() && lastProp->hasSlot(),
1272 lastProp->slot == fixed - 1);
1273 revertToFixedSlots(cx);
1278 /* On the way out, consider shrinking table if its load factor is <= .25. */
1279 if (lastProp->hasTable()) {
1280 PropertyTable *table = lastProp->getTable();
1281 uint32 size = table->capacity();
1282 if (size > PropertyTable::MIN_SIZE && table->entryCount <= size >> 2) {
1284 (void) table->change(-1, cx);
1288 CHECK_SHAPE_CONSISTENCY(this);
1289 LIVE_SCOPE_METER(cx, --cx->runtime->liveObjectProps);
1295 JSObject::clear(JSContext *cx)
1297 LIVE_SCOPE_METER(cx, cx->runtime->liveObjectProps -= propertyCount());
1299 Shape *shape = lastProp;
1300 JS_ASSERT(inDictionaryMode() == shape->inDictionary());
1302 while (shape->parent) {
1303 shape = shape->parent;
1304 JS_ASSERT(inDictionaryMode() == shape->inDictionary());
1306 JS_ASSERT(shape->isEmptyShape());
1308 if (inDictionaryMode())
1309 shape->listp = &lastProp;
1312 * Revert to fixed slots if we have cleared below the first dynamically
1313 * allocated slot, preserving invariant that objects with the same shape
1314 * use the fixed slots in the same way.
1316 if (hasSlotsArray() && JSSLOT_FREE(getClass()) <= numFixedSlots())
1317 revertToFixedSlots(cx);
1320 * We have rewound to a uniquely-shaped empty scope, so we don't need an
1321 * override for this object's shape.
1326 LeaveTraceIfGlobalObject(cx, this);
1327 JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals);
1328 CHECK_SHAPE_CONSISTENCY(this);
1332 JSObject::generateOwnShape(JSContext *cx)
1335 JS_ASSERT_IF(!parent && JS_ON_TRACE(cx), JS_TRACE_MONITOR_ON_TRACE(cx)->bailExit);
1336 LeaveTraceIfGlobalObject(cx, this);
1339 * If we are recording, here is where we forget already-guarded shapes.
1340 * Any subsequent property operation upon object on the trace currently
1341 * being recorded will re-guard (and re-memoize).
1343 if (TraceRecorder *tr = TRACE_RECORDER(cx))
1344 tr->forgetGuardedShapesForObject(this);
1347 setOwnShape(js_GenerateShape(cx));
1351 JSObject::deletingShapeChange(JSContext *cx, const Shape &shape)
1353 JS_ASSERT(!JSID_IS_VOID(shape.id));
1354 generateOwnShape(cx);
1358 JSObject::methodShapeChange(JSContext *cx, const Shape &shape)
1360 const Shape *result = &shape;
1362 JS_ASSERT(!JSID_IS_VOID(shape.id));
1363 if (shape.isMethod()) {
1365 const Value &prev = nativeGetSlot(shape.slot);
1366 JS_ASSERT(&shape.methodObject() == &prev.toObject());
1367 JS_ASSERT(canHaveMethodBarrier());
1368 JS_ASSERT(hasMethodBarrier());
1369 JS_ASSERT(!shape.rawSetter || shape.rawSetter == js_watch_set);
1373 * Pass null to make a stub getter, but pass along shape.rawSetter to
1374 * preserve watchpoints. Clear Shape::METHOD from flags as we are
1375 * despecializing from a method memoized in the property tree to a
1376 * plain old function-valued property.
1378 result = putProperty(cx, shape.id, NULL, shape.rawSetter, shape.slot,
1380 shape.getFlags() & ~Shape::METHOD,
1387 uintN thrashCount = getMethodThrashCount();
1388 if (thrashCount < JSObject::METHOD_THRASH_COUNT_MAX) {
1390 setMethodThrashCount(thrashCount);
1391 if (thrashCount == JSObject::METHOD_THRASH_COUNT_MAX) {
1398 generateOwnShape(cx);
1403 JSObject::methodShapeChange(JSContext *cx, uint32 slot)
1405 if (!hasMethodBarrier()) {
1406 generateOwnShape(cx);
1408 for (Shape::Range r = lastProp->all(); !r.empty(); r.popFront()) {
1409 const Shape &shape = r.front();
1410 JS_ASSERT(!JSID_IS_VOID(shape.id));
1411 if (shape.slot == slot)
1412 return methodShapeChange(cx, shape) != NULL;
1419 JSObject::protoShapeChange(JSContext *cx)
1421 generateOwnShape(cx);
1425 JSObject::shadowingShapeChange(JSContext *cx, const Shape &shape)
1427 JS_ASSERT(!JSID_IS_VOID(shape.id));
1428 generateOwnShape(cx);
1432 JSObject::globalObjectOwnShapeChange(JSContext *cx)
1434 generateOwnShape(cx);
1435 return !js_IsPropertyCacheDisabled(cx);
1440 PrintPropertyGetterOrSetter(JSTracer *trc, char *buf, size_t bufsize)
1447 JS_ASSERT(trc->debugPrinter == PrintPropertyGetterOrSetter);
1448 shape = (Shape *)trc->debugPrintArg;
1450 JS_ASSERT(!JSID_IS_VOID(id));
1451 name = trc->debugPrintIndex ? js_setter_str : js_getter_str;
1453 if (JSID_IS_ATOM(id)) {
1454 n = PutEscapedString(buf, bufsize, JSID_TO_ATOM(id), 0);
1456 JS_snprintf(buf + n, bufsize - n, " %s", name);
1457 } else if (JSID_IS_INT(shape->id)) {
1458 JS_snprintf(buf, bufsize, "%d %s", JSID_TO_INT(id), name);
1460 JS_snprintf(buf, bufsize, "<object> %s", name);
1465 PrintPropertyMethod(JSTracer *trc, char *buf, size_t bufsize)
1471 JS_ASSERT(trc->debugPrinter == PrintPropertyMethod);
1472 shape = (Shape *)trc->debugPrintArg;
1474 JS_ASSERT(!JSID_IS_VOID(id));
1476 JS_ASSERT(JSID_IS_ATOM(id));
1477 n = PutEscapedString(buf, bufsize, JSID_TO_ATOM(id), 0);
1479 JS_snprintf(buf + n, bufsize - n, " method");
1484 Shape::trace(JSTracer *trc) const
1486 if (IS_GC_MARKING_TRACER(trc))
1489 MarkId(trc, id, "id");
1491 if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) {
1492 if ((attrs & JSPROP_GETTER) && rawGetter) {
1493 JS_SET_TRACING_DETAILS(trc, PrintPropertyGetterOrSetter, this, 0);
1494 Mark(trc, getterObject());
1496 if ((attrs & JSPROP_SETTER) && rawSetter) {
1497 JS_SET_TRACING_DETAILS(trc, PrintPropertyGetterOrSetter, this, 1);
1498 Mark(trc, setterObject());
1503 JS_SET_TRACING_DETAILS(trc, PrintPropertyMethod, this, 0);
1504 Mark(trc, &methodObject());