/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sw=4 et tw=78: * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Communicator client code, released * March 31, 1998. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * JS symbol tables. */ #include #include #include #include "jstypes.h" #include "jsstdint.h" #include "jsarena.h" #include "jsbit.h" #include "jsclist.h" #include "jsdhash.h" #include "jsutil.h" #include "jsapi.h" #include "jsatom.h" #include "jscntxt.h" #include "jsdbgapi.h" #include "jsfun.h" /* for JS_ARGS_LENGTH_MAX */ #include "jslock.h" #include "jsnum.h" #include "jsobj.h" #include "jsscope.h" #include "jsstr.h" #include "jstracer.h" #include "jsdbgapiinlines.h" #include "jsobjinlines.h" #include "jsscopeinlines.h" using namespace js; using namespace js::gc; uint32 js_GenerateShape(JSRuntime *rt) { uint32 shape; shape = JS_ATOMIC_INCREMENT(&rt->shapeGen); JS_ASSERT(shape != 0); if (shape >= SHAPE_OVERFLOW_BIT) { /* * FIXME bug 440834: The shape id space has overflowed. Currently we * cope badly with this and schedule the GC on the every call. But * first we make sure that increments from other threads would not * have a chance to wrap around shapeGen to zero. */ rt->shapeGen = SHAPE_OVERFLOW_BIT; shape = SHAPE_OVERFLOW_BIT; #ifdef JS_THREADSAFE AutoLockGC lockIf(rt); #endif TriggerGC(rt); } return shape; } uint32 js_GenerateShape(JSContext *cx) { return js_GenerateShape(cx->runtime); } bool JSObject::ensureClassReservedSlotsForEmptyObject(JSContext *cx) { JS_ASSERT(nativeEmpty()); /* * Subtle rule: objects that call JSObject::ensureInstanceReservedSlots * must either: * * (a) never escape anywhere an ad-hoc property could be set on them; or * * (b) protect their instance-reserved slots with shapes, at least a custom * empty shape with the right slotSpan member. * * Block objects are the only objects that fall into category (a). While * Call objects cannot escape, they can grow ad-hoc properties via eval * of a var declaration, or due to a function statement being evaluated, * but they have slots mapped by compiler-created shapes, and thus (b) no * problem predicting first ad-hoc property slot. Bound Function objects * have a custom empty shape. * * (Note that Block, Call, and bound Function objects are the only native * class objects that are allowed to call ensureInstanceReservedSlots.) */ uint32 nfixed = JSSLOT_FREE(getClass()); if (nfixed > numSlots() && !allocSlots(cx, nfixed)) return false; return true; } #define PROPERTY_TABLE_NBYTES(n) ((n) * sizeof(Shape *)) #ifdef DEBUG JS_FRIEND_DATA(JSScopeStats) js_scope_stats = {0}; # define METER(x) JS_ATOMIC_INCREMENT(&js_scope_stats.x) #else # define METER(x) ((void) 0) #endif bool PropertyTable::init(JSRuntime *rt, Shape *lastProp) { /* * Either we're creating a table for a large scope that was populated * via property cache hit logic under JSOP_INITPROP, JSOP_SETNAME, or * JSOP_SETPROP; or else calloc failed at least once already. In any * event, let's try to grow, overallocating to hold at least twice the * current population. */ uint32 sizeLog2 = JS_CeilingLog2(2 * entryCount); if (sizeLog2 < MIN_SIZE_LOG2) sizeLog2 = MIN_SIZE_LOG2; /* * Use rt->calloc_ for memory accounting and overpressure handling * without OOM reporting. See PropertyTable::change. */ entries = (Shape **) rt->calloc_(JS_BIT(sizeLog2) * sizeof(Shape *)); if (!entries) { METER(tableAllocFails); return false; } hashShift = JS_DHASH_BITS - sizeLog2; for (Shape::Range r = lastProp->all(); !r.empty(); r.popFront()) { const Shape &shape = r.front(); METER(searches); METER(initSearches); Shape **spp = search(shape.propid, true); /* * Beware duplicate args and arg vs. var conflicts: the youngest shape * (nearest to lastProp) must win. See bug 600067. */ if (!SHAPE_FETCH(spp)) SHAPE_STORE_PRESERVING_COLLISION(spp, &shape); } return true; } bool Shape::hashify(JSRuntime *rt) { JS_ASSERT(!hasTable()); PropertyTable *table = rt->new_(entryCount()); if (!table) return false; setTable(table); return getTable()->init(rt, this); } #ifdef DEBUG # include "jsprf.h" # define LIVE_SCOPE_METER(cx,expr) JS_LOCK_RUNTIME_VOID(cx->runtime,expr) #else # define LIVE_SCOPE_METER(cx,expr) /* nothing */ #endif JS_STATIC_ASSERT(sizeof(JSHashNumber) == 4); JS_STATIC_ASSERT(sizeof(jsid) == JS_BYTES_PER_WORD); #if JS_BYTES_PER_WORD == 4 # define HASH_ID(id) ((JSHashNumber)(JSID_BITS(id))) #elif JS_BYTES_PER_WORD == 8 # define HASH_ID(id) ((JSHashNumber)(JSID_BITS(id)) ^ (JSHashNumber)((JSID_BITS(id)) >> 32)) #else # error "Unsupported configuration" #endif /* * Double hashing needs the second hash code to be relatively prime to table * size, so we simply make hash2 odd. The inputs to multiplicative hash are * the golden ratio, expressed as a fixed-point 32 bit fraction, and the id * itself. */ #define HASH0(id) (HASH_ID(id) * JS_GOLDEN_RATIO) #define HASH1(hash0,shift) ((hash0) >> (shift)) #define HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1) Shape ** PropertyTable::search(jsid id, bool adding) { JSHashNumber hash0, hash1, hash2; int sizeLog2; Shape *stored, *shape, **spp, **firstRemoved; uint32 sizeMask; JS_ASSERT(entries); JS_ASSERT(!JSID_IS_VOID(id)); /* Compute the primary hash address. */ METER(hashes); hash0 = HASH0(id); hash1 = HASH1(hash0, hashShift); spp = entries + hash1; /* Miss: return space for a new entry. */ stored = *spp; if (SHAPE_IS_FREE(stored)) { METER(misses); METER(hashMisses); return spp; } /* Hit: return entry. */ shape = SHAPE_CLEAR_COLLISION(stored); if (shape && shape->propid == id) { METER(hits); METER(hashHits); return spp; } /* Collision: double hash. */ sizeLog2 = JS_DHASH_BITS - hashShift; hash2 = HASH2(hash0, sizeLog2, hashShift); sizeMask = JS_BITMASK(sizeLog2); #ifdef DEBUG jsuword collision_flag = SHAPE_COLLISION; #endif /* Save the first removed entry pointer so we can recycle it if adding. */ if (SHAPE_IS_REMOVED(stored)) { firstRemoved = spp; } else { firstRemoved = NULL; if (adding && !SHAPE_HAD_COLLISION(stored)) SHAPE_FLAG_COLLISION(spp, shape); #ifdef DEBUG collision_flag &= jsuword(*spp) & SHAPE_COLLISION; #endif } for (;;) { METER(steps); hash1 -= hash2; hash1 &= sizeMask; spp = entries + hash1; stored = *spp; if (SHAPE_IS_FREE(stored)) { METER(misses); METER(stepMisses); return (adding && firstRemoved) ? firstRemoved : spp; } shape = SHAPE_CLEAR_COLLISION(stored); if (shape && shape->propid == id) { METER(hits); METER(stepHits); JS_ASSERT(collision_flag); return spp; } if (SHAPE_IS_REMOVED(stored)) { if (!firstRemoved) firstRemoved = spp; } else { if (adding && !SHAPE_HAD_COLLISION(stored)) SHAPE_FLAG_COLLISION(spp, shape); #ifdef DEBUG collision_flag &= jsuword(*spp) & SHAPE_COLLISION; #endif } } /* NOTREACHED */ return NULL; } bool PropertyTable::change(int log2Delta, JSContext *cx) { int oldlog2, newlog2; uint32 oldsize, newsize, nbytes; Shape **newTable, **oldTable, **spp, **oldspp, *shape; JS_ASSERT(entries); /* * Grow, shrink, or compress by changing this->entries. */ oldlog2 = JS_DHASH_BITS - hashShift; newlog2 = oldlog2 + log2Delta; oldsize = JS_BIT(oldlog2); newsize = JS_BIT(newlog2); nbytes = PROPERTY_TABLE_NBYTES(newsize); newTable = (Shape **) cx->calloc_(nbytes); if (!newTable) { METER(tableAllocFails); return false; } /* Now that we have newTable allocated, update members. */ hashShift = JS_DHASH_BITS - newlog2; removedCount = 0; oldTable = entries; entries = newTable; /* Copy only live entries, leaving removed and free ones behind. */ for (oldspp = oldTable; oldsize != 0; oldspp++) { shape = SHAPE_FETCH(oldspp); if (shape) { METER(searches); METER(changeSearches); spp = search(shape->propid, true); JS_ASSERT(SHAPE_IS_FREE(*spp)); *spp = shape; } oldsize--; } /* Finally, free the old entries storage. */ cx->free_(oldTable); return true; } bool PropertyTable::grow(JSContext *cx) { JS_ASSERT(needsToGrow()); uint32 size = capacity(); int delta = removedCount < size >> 2; if (!delta) METER(compresses); else METER(grows); if (!change(delta, cx) && entryCount + removedCount == size - 1) { JS_ReportOutOfMemory(cx); return false; } return true; } Shape * Shape::getChild(JSContext *cx, const js::Shape &child, Shape **listp) { JS_ASSERT(!JSID_IS_VOID(child.propid)); JS_ASSERT(!child.inDictionary()); if (inDictionary()) { Shape *oldShape = *listp; PropertyTable *table = (oldShape && oldShape->hasTable()) ? oldShape->getTable() : NULL; /* * Attempt to grow table if needed before extending *listp, rather than * risking OOM under table->grow after newDictionaryShape succeeds, and * then have to fix up *listp. */ if (table && table->needsToGrow() && !table->grow(cx)) return NULL; if (newDictionaryShape(cx, child, listp)) { Shape *newShape = *listp; JS_ASSERT(oldShape == newShape->parent); if (table) { /* Add newShape to the property table. */ METER(searches); Shape **spp = table->search(newShape->propid, true); /* * Beware duplicate formal parameters, allowed by ECMA-262 in * non-strict mode. Otherwise we know that Bindings::add (our * caller) won't pass an id already in the table to us. In the * case of duplicate formals, the last one wins, so while we * must not overcount entries, we must store newShape. */ if (!SHAPE_FETCH(spp)) ++table->entryCount; SHAPE_STORE_PRESERVING_COLLISION(spp, newShape); /* Hand the table off from oldShape to newShape. */ oldShape->setTable(NULL); newShape->setTable(table); } else { if (!newShape->hasTable()) newShape->hashify(cx->runtime); } return newShape; } return NULL; } if ((*listp)->entryCount() >= PropertyTree::MAX_HEIGHT) { Shape *dprop = Shape::newDictionaryList(cx, listp); if (!dprop) return NULL; return dprop->getChild(cx, child, listp); } Shape *shape = JS_PROPERTY_TREE(cx).getChild(cx, this, child); if (shape) { JS_ASSERT(shape->parent == this); JS_ASSERT(this == *listp); *listp = shape; } return shape; } /* * Get or create a property-tree or dictionary child property of parent, which * must be lastProp if inDictionaryMode(), else parent must be one of lastProp * or lastProp->parent. */ Shape * JSObject::getChildProperty(JSContext *cx, Shape *parent, Shape &child) { JS_ASSERT(!JSID_IS_VOID(child.propid)); JS_ASSERT(!child.inDictionary()); /* * Aliases share another property's slot, passed in the |slot| parameter. * Shared properties have no slot. Unshared properties that do not alias * another property's slot allocate a slot here, but may lose it due to a * JS_ClearScope call. */ if (!child.isAlias()) { if (child.attrs & JSPROP_SHARED) { child.slot = SHAPE_INVALID_SLOT; } else { /* * We may have set slot from a nearly-matching shape, above. If so, * we're overwriting that nearly-matching shape, so we can reuse * its slot -- we don't need to allocate a new one. Similarly, we * use a specific slot if provided by the caller. */ if (child.slot == SHAPE_INVALID_SLOT && !allocSlot(cx, &child.slot)) return NULL; } } Shape *shape; if (inDictionaryMode()) { JS_ASSERT(parent == lastProp); if (parent->frozen()) { parent = Shape::newDictionaryList(cx, &lastProp); if (!parent) return NULL; JS_ASSERT(!parent->frozen()); } shape = Shape::newDictionaryShape(cx, child, &lastProp); if (!shape) return NULL; } else { shape = JS_PROPERTY_TREE(cx).getChild(cx, parent, child); if (!shape) return NULL; JS_ASSERT(shape->parent == parent); JS_ASSERT_IF(parent != lastProp, parent == lastProp->parent); setLastProperty(shape); } updateFlags(shape); updateShape(cx); return shape; } Shape * Shape::newDictionaryShape(JSContext *cx, const Shape &child, Shape **listp) { Shape *dprop = JS_PROPERTY_TREE(cx).newShape(cx); if (!dprop) return NULL; new (dprop) Shape(child.propid, child.rawGetter, child.rawSetter, child.slot, child.attrs, (child.flags & ~FROZEN) | IN_DICTIONARY, child.shortid, js_GenerateShape(cx), child.slotSpan); dprop->listp = NULL; dprop->insertIntoDictionary(listp); JS_COMPARTMENT_METER(cx->compartment->liveDictModeNodes++); return dprop; } Shape * Shape::newDictionaryList(JSContext *cx, Shape **listp) { Shape *shape = *listp; Shape *list = shape; /* * We temporarily create the dictionary shapes using a root located on the * stack. This way, the GC doesn't see any intermediate state until we * switch listp at the end. */ Shape *root = NULL; Shape **childp = &root; while (shape) { JS_ASSERT_IF(!shape->frozen(), !shape->inDictionary()); Shape *dprop = Shape::newDictionaryShape(cx, *shape, childp); if (!dprop) { METER(toDictFails); *listp = list; return NULL; } JS_ASSERT(!dprop->hasTable()); childp = &dprop->parent; shape = shape->parent; } *listp = root; root->listp = listp; JS_ASSERT(root->inDictionary()); root->hashify(cx->runtime); return root; } bool JSObject::toDictionaryMode(JSContext *cx) { JS_ASSERT(!inDictionaryMode()); /* We allocate the shapes from cx->compartment, so make sure it's right. */ JS_ASSERT(compartment() == cx->compartment); if (!Shape::newDictionaryList(cx, &lastProp)) return false; clearOwnShape(); return true; } /* * Normalize stub getter and setter values for faster is-stub testing in the * SHAPE_CALL_[GS]ETTER macros. */ static inline bool NormalizeGetterAndSetter(JSContext *cx, JSObject *obj, jsid id, uintN attrs, uintN flags, PropertyOp &getter, StrictPropertyOp &setter) { if (setter == StrictPropertyStub) { JS_ASSERT(!(attrs & JSPROP_SETTER)); setter = NULL; } if (flags & Shape::METHOD) { /* Here, getter is the method, a function object reference. */ JS_ASSERT(getter); JS_ASSERT(!setter || setter == js_watch_set); JS_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER))); } else { if (getter == PropertyStub) { JS_ASSERT(!(attrs & JSPROP_GETTER)); getter = NULL; } } return true; } #ifdef DEBUG # define CHECK_SHAPE_CONSISTENCY(obj) obj->checkShapeConsistency() void JSObject::checkShapeConsistency() { static int throttle = -1; if (throttle < 0) { if (const char *var = getenv("JS_CHECK_SHAPE_THROTTLE")) throttle = atoi(var); if (throttle < 0) throttle = 0; } if (throttle == 0) return; JS_ASSERT(isNative()); if (hasOwnShape()) JS_ASSERT(objShape != lastProp->shapeid); else JS_ASSERT(objShape == lastProp->shapeid); Shape *shape = lastProp; Shape *prev = NULL; if (inDictionaryMode()) { if (shape->hasTable()) { PropertyTable *table = shape->getTable(); for (uint32 fslot = table->freelist; fslot != SHAPE_INVALID_SLOT; fslot = getSlotRef(fslot).toPrivateUint32()) { JS_ASSERT(fslot < shape->slotSpan); } for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) { JS_ASSERT_IF(shape != lastProp, !shape->hasTable()); Shape **spp = table->search(shape->propid, false); JS_ASSERT(SHAPE_FETCH(spp) == shape); } } else { shape = shape->parent; for (int n = throttle; --n >= 0 && shape; shape = shape->parent) JS_ASSERT(!shape->hasTable()); } shape = lastProp; for (int n = throttle; --n >= 0 && shape; shape = shape->parent) { JS_ASSERT_IF(shape->slot != SHAPE_INVALID_SLOT, shape->slot < shape->slotSpan); if (!prev) { JS_ASSERT(shape == lastProp); JS_ASSERT(shape->listp == &lastProp); } else { JS_ASSERT(shape->listp == &prev->parent); JS_ASSERT(prev->slotSpan >= shape->slotSpan); } prev = shape; } } else { for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) { if (shape->hasTable()) { PropertyTable *table = shape->getTable(); JS_ASSERT(shape->parent); for (Shape::Range r(shape); !r.empty(); r.popFront()) { Shape **spp = table->search(r.front().propid, false); JS_ASSERT(SHAPE_FETCH(spp) == &r.front()); } } if (prev) { JS_ASSERT(prev->slotSpan >= shape->slotSpan); shape->kids.checkConsistency(prev); } prev = shape; } } } #else # define CHECK_SHAPE_CONSISTENCY(obj) ((void)0) #endif const Shape * JSObject::addProperty(JSContext *cx, jsid id, PropertyOp getter, StrictPropertyOp setter, uint32 slot, uintN attrs, uintN flags, intN shortid) { JS_ASSERT(!JSID_IS_VOID(id)); if (!isExtensible()) { reportNotExtensible(cx); return NULL; } NormalizeGetterAndSetter(cx, this, id, attrs, flags, getter, setter); /* Search for id with adding = true in order to claim its entry. */ Shape **spp = nativeSearch(id, true); JS_ASSERT(!SHAPE_FETCH(spp)); const Shape *shape = addPropertyInternal(cx, id, getter, setter, slot, attrs, flags, shortid, spp); if (!shape) return NULL; /* Update any watchpoints referring to this property. */ shape = js_UpdateWatchpointsForShape(cx, this, shape); if (!shape) METER(wrapWatchFails); return shape; } const Shape * JSObject::addPropertyInternal(JSContext *cx, jsid id, PropertyOp getter, StrictPropertyOp setter, uint32 slot, uintN attrs, uintN flags, intN shortid, Shape **spp) { JS_ASSERT_IF(inDictionaryMode(), !lastProp->frozen()); PropertyTable *table = NULL; if (!inDictionaryMode()) { if (lastProp->entryCount() >= PropertyTree::MAX_HEIGHT) { if (!toDictionaryMode(cx)) return NULL; spp = nativeSearch(id, true); table = lastProp->getTable(); } } else if (lastProp->hasTable()) { table = lastProp->getTable(); if (table->needsToGrow()) { if (!table->grow(cx)) return NULL; METER(searches); METER(changeSearches); spp = table->search(id, true); JS_ASSERT(!SHAPE_FETCH(spp)); } } /* Find or create a property tree node labeled by our arguments. */ const Shape *shape; { Shape child(id, getter, setter, slot, attrs, flags, shortid); shape = getChildProperty(cx, lastProp, child); } if (shape) { JS_ASSERT(shape == lastProp); if (table) { /* Store the tree node pointer in the table entry for id. */ SHAPE_STORE_PRESERVING_COLLISION(spp, shape); ++table->entryCount; /* Pass the table along to the new lastProp, namely shape. */ JS_ASSERT(shape->parent->getTable() == table); shape->parent->setTable(NULL); shape->setTable(table); } CHECK_SHAPE_CONSISTENCY(this); METER(adds); return shape; } CHECK_SHAPE_CONSISTENCY(this); METER(addFails); return NULL; } /* * Check and adjust the new attributes for the shape to make sure that our * slot access optimizations are sound. It is responsibility of the callers to * enforce all restrictions from ECMA-262 v5 8.12.9 [[DefineOwnProperty]]. */ inline bool CheckCanChangeAttrs(JSContext *cx, JSObject *obj, const Shape *shape, uintN *attrsp) { if (shape->configurable()) return true; /* A permanent property must stay permanent. */ *attrsp |= JSPROP_PERMANENT; /* Reject attempts to remove a slot from the permanent data property. */ if (shape->isDataDescriptor() && shape->hasSlot() && (*attrsp & (JSPROP_GETTER | JSPROP_SETTER | JSPROP_SHARED))) { obj->reportNotConfigurable(cx, shape->propid); return false; } return true; } const Shape * JSObject::putProperty(JSContext *cx, jsid id, PropertyOp getter, StrictPropertyOp setter, uint32 slot, uintN attrs, uintN flags, intN shortid) { JS_ASSERT(!JSID_IS_VOID(id)); /* * Horrid non-strict eval, debuggers, and |default xml namespace ...| may * extend Call objects. */ if (lastProp->frozen()) { if (!Shape::newDictionaryList(cx, &lastProp)) return NULL; JS_ASSERT(!lastProp->frozen()); } NormalizeGetterAndSetter(cx, this, id, attrs, flags, getter, setter); /* Search for id in order to claim its entry if table has been allocated. */ Shape **spp = nativeSearch(id, true); Shape *shape = SHAPE_FETCH(spp); if (!shape) { /* * You can't add properties to a non-extensible object, but you can change * attributes of properties in such objects. */ if (!isExtensible()) { reportNotExtensible(cx); return NULL; } const Shape *newShape = addPropertyInternal(cx, id, getter, setter, slot, attrs, flags, shortid, spp); if (!newShape) return NULL; newShape = js_UpdateWatchpointsForShape(cx, this, newShape); if (!newShape) METER(wrapWatchFails); return newShape; } /* Property exists: search must have returned a valid *spp. */ JS_ASSERT(!SHAPE_IS_REMOVED(*spp)); if (!CheckCanChangeAttrs(cx, this, shape, &attrs)) return NULL; /* * If the caller wants to allocate a slot, but doesn't care which slot, * copy the existing shape's slot into slot so we can match shape, if all * other members match. */ bool hadSlot = !shape->isAlias() && shape->hasSlot(); uint32 oldSlot = shape->slot; if (!(attrs & JSPROP_SHARED) && slot == SHAPE_INVALID_SLOT && hadSlot) slot = oldSlot; /* * Now that we've possibly preserved slot, check whether all members match. * If so, this is a redundant "put" and we can return without more work. */ if (shape->matchesParamsAfterId(getter, setter, slot, attrs, flags, shortid)) { METER(redundantPuts); return shape; } /* * Overwriting a non-last property requires switching to dictionary mode. * The shape tree is shared immutable, and we can't removeProperty and then * addPropertyInternal because a failure under add would lose data. */ if (shape != lastProp && !inDictionaryMode()) { if (!toDictionaryMode(cx)) return NULL; spp = nativeSearch(shape->propid); shape = SHAPE_FETCH(spp); } /* * Now that we have passed the lastProp->frozen() check at the top of this * method, and the non-last-property conditioning just above, we are ready * to overwrite. * * Optimize the case of a non-frozen dictionary-mode object based on the * property that dictionaries exclusively own their mutable shape structs, * each of which has a unique shape number (not shared via a shape tree). * * This is more than an optimization: it is required to preserve for-in * enumeration order (see bug 601399). */ if (inDictionaryMode()) { /* FIXME bug 593129 -- slot allocation and JSObject *this must move out of here! */ if (slot == SHAPE_INVALID_SLOT && !(attrs & JSPROP_SHARED) && !(flags & Shape::ALIAS)) { if (!allocSlot(cx, &slot)) return NULL; } shape->slot = slot; if (slot != SHAPE_INVALID_SLOT && slot >= shape->slotSpan) { shape->slotSpan = slot + 1; for (Shape *temp = lastProp; temp != shape; temp = temp->parent) { if (temp->slotSpan <= slot) temp->slotSpan = slot + 1; } } shape->rawGetter = getter; shape->rawSetter = setter; shape->attrs = uint8(attrs); shape->flags = flags | Shape::IN_DICTIONARY; shape->shortid = int16(shortid); /* * We are done updating shape and lastProp. Now we may need to update * flags and we will need to update objShape, which is no longer "own". * In the last non-dictionary property case in the else clause just * below, getChildProperty handles this for us. First update flags. */ updateFlags(shape); /* * We have just mutated shape in place, but nothing caches it based on * shape->shape unless shape is lastProp and !hasOwnShape()). Therefore * we regenerate only lastProp->shape. We will clearOwnShape(), which * sets objShape to lastProp->shape. */ lastProp->shapeid = js_GenerateShape(cx); clearOwnShape(); } else { /* * Updating lastProp in a non-dictionary-mode object. Such objects * share their shapes via a tree rooted at a prototype emptyShape, or * perhaps a well-known compartment-wide singleton emptyShape. * * If any shape in the tree has a property hashtable, it is shared and * immutable too, therefore we must not update *spp. */ JS_ASSERT(shape == lastProp); removeLastProperty(); /* Find or create a property tree node labeled by our arguments. */ Shape child(id, getter, setter, slot, attrs, flags, shortid); Shape *newShape = getChildProperty(cx, lastProp, child); if (!newShape) { setLastProperty(shape); CHECK_SHAPE_CONSISTENCY(this); METER(putFails); return NULL; } shape = newShape; } /* * Can't fail now, so free the previous incarnation's slot if the new shape * has no slot. But we do not need to free oldSlot (and must not, as trying * to will botch an assertion in JSObject::freeSlot) if the new lastProp * (shape here) has a slotSpan that does not cover it. */ if (hadSlot && !shape->hasSlot()) { if (oldSlot < shape->slotSpan) freeSlot(cx, oldSlot); else getSlotRef(oldSlot).setUndefined(); JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals); } CHECK_SHAPE_CONSISTENCY(this); METER(puts); const Shape *newShape = js_UpdateWatchpointsForShape(cx, this, shape); if (!newShape) METER(wrapWatchFails); return newShape; } const Shape * JSObject::changeProperty(JSContext *cx, const Shape *shape, uintN attrs, uintN mask, PropertyOp getter, StrictPropertyOp setter) { JS_ASSERT_IF(inDictionaryMode(), !lastProp->frozen()); JS_ASSERT(!JSID_IS_VOID(shape->propid)); JS_ASSERT(nativeContains(*shape)); attrs |= shape->attrs & mask; /* Allow only shared (slotless) => unshared (slotful) transition. */ JS_ASSERT(!((attrs ^ shape->attrs) & JSPROP_SHARED) || !(attrs & JSPROP_SHARED)); /* Don't allow method properties to be changed to have a getter. */ JS_ASSERT_IF(getter != shape->rawGetter, !shape->isMethod()); if (getter == PropertyStub) getter = NULL; if (setter == StrictPropertyStub) setter = NULL; if (!CheckCanChangeAttrs(cx, this, shape, &attrs)) return NULL; if (shape->attrs == attrs && shape->getter() == getter && shape->setter() == setter) return shape; const Shape *newShape; /* * Dictionary-mode objects exclusively own their mutable shape structs, so * we simply modify in place. */ if (inDictionaryMode()) { /* FIXME bug 593129 -- slot allocation and JSObject *this must move out of here! */ uint32 slot = shape->slot; if (slot == SHAPE_INVALID_SLOT && !(attrs & JSPROP_SHARED) && !(flags & Shape::ALIAS)) { if (!allocSlot(cx, &slot)) return NULL; } Shape *mutableShape = const_cast(shape); mutableShape->slot = slot; if (slot != SHAPE_INVALID_SLOT && slot >= shape->slotSpan) { mutableShape->slotSpan = slot + 1; for (Shape *temp = lastProp; temp != shape; temp = temp->parent) { if (temp->slotSpan <= slot) temp->slotSpan = slot + 1; } } mutableShape->rawGetter = getter; mutableShape->rawSetter = setter; mutableShape->attrs = uint8(attrs); updateFlags(shape); /* See the corresponding code in putProperty. */ lastProp->shapeid = js_GenerateShape(cx); clearOwnShape(); shape = js_UpdateWatchpointsForShape(cx, this, shape); if (!shape) { METER(wrapWatchFails); return NULL; } JS_ASSERT(shape == mutableShape); newShape = mutableShape; } else if (shape == lastProp) { Shape child(shape->propid, getter, setter, shape->slot, attrs, shape->flags, shape->shortid); newShape = getChildProperty(cx, shape->parent, child); #ifdef DEBUG if (newShape) { JS_ASSERT(newShape == lastProp); if (newShape->hasTable()) { Shape **spp = nativeSearch(shape->propid); JS_ASSERT(SHAPE_FETCH(spp) == newShape); } } #endif } else { /* * Let JSObject::putProperty handle this |overwriting| case, including * the conservation of shape->slot (if it's valid). We must not call * removeProperty because it will free an allocated shape->slot, and * putProperty won't re-allocate it. */ Shape child(shape->propid, getter, setter, shape->slot, attrs, shape->flags, shape->shortid); newShape = putProperty(cx, child.propid, child.rawGetter, child.rawSetter, child.slot, child.attrs, child.flags, child.shortid); #ifdef DEBUG if (newShape) METER(changePuts); #endif } #ifdef DEBUG CHECK_SHAPE_CONSISTENCY(this); if (newShape) METER(changes); else METER(changeFails); #endif return newShape; } bool JSObject::removeProperty(JSContext *cx, jsid id) { Shape **spp = nativeSearch(id); Shape *shape = SHAPE_FETCH(spp); if (!shape) { METER(uselessRemoves); return true; } /* First, if shape is unshared and not has a slot, free its slot number. */ bool addedToFreelist = false; bool hadSlot = !shape->isAlias() && shape->hasSlot(); if (hadSlot) { addedToFreelist = freeSlot(cx, shape->slot); JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals); } /* If shape is not the last property added, switch to dictionary mode. */ if (shape != lastProp && !inDictionaryMode()) { if (!toDictionaryMode(cx)) return false; spp = nativeSearch(shape->propid); shape = SHAPE_FETCH(spp); } /* * A dictionary-mode object owns mutable, unique shapes on a non-circular * doubly linked list, optionally hashed by lastProp->table. So we can edit * the list and hash in place. */ if (inDictionaryMode()) { PropertyTable *table = lastProp->hasTable() ? lastProp->getTable() : NULL; if (SHAPE_HAD_COLLISION(*spp)) { JS_ASSERT(table); *spp = SHAPE_REMOVED; ++table->removedCount; --table->entryCount; } else { METER(removeFrees); if (table) { *spp = NULL; --table->entryCount; #ifdef DEBUG /* * Check the consistency of the table but limit the number of * checks not to alter significantly the complexity of the * delete in debug builds, see bug 534493. */ const Shape *aprop = lastProp; for (int n = 50; --n >= 0 && aprop->parent; aprop = aprop->parent) JS_ASSERT_IF(aprop != shape, nativeContains(*aprop)); #endif } } /* * Remove shape from its non-circular doubly linked list, setting this * object's OWN_SHAPE flag so the updateShape(cx) further below will * generate a fresh shape id for this object, distinct from the id of * any shape in the list. We need a fresh shape for all deletions, even * of lastProp. Otherwise, a shape number could replay and caches might * return get deleted DictionaryShapes! See bug 595365. */ flags |= OWN_SHAPE; Shape *oldLastProp = lastProp; shape->removeFromDictionary(this); if (table) { if (shape == oldLastProp) { JS_ASSERT(shape->getTable() == table); JS_ASSERT(shape->parent == lastProp); JS_ASSERT(shape->slotSpan >= lastProp->slotSpan); JS_ASSERT_IF(hadSlot, shape->slot + 1 <= shape->slotSpan); /* * Maintain slot freelist consistency. Slot numbers on the * freelist are less than lastProp->slotSpan; so if the * freelist is non-empty, then lastProp->slotSpan may not * decrease. */ if (table->freelist != SHAPE_INVALID_SLOT) { lastProp->slotSpan = shape->slotSpan; /* * Add the slot to the freelist if it wasn't added in * freeSlot and it is not a reserved slot. */ if (hadSlot && !addedToFreelist && JSSLOT_FREE(clasp) <= shape->slot) { getSlotRef(shape->slot).setPrivateUint32(table->freelist); table->freelist = shape->slot; } } } /* Hand off table from old to new lastProp. */ oldLastProp->setTable(NULL); lastProp->setTable(table); } } else { /* * Non-dictionary-mode property tables are shared immutables, so all we * need do is retract lastProp and we'll either get or else lazily make * via a later hashify the exact table for the new property lineage. */ JS_ASSERT(shape == lastProp); removeLastProperty(); /* * Revert to fixed slots if this was the first dynamically allocated slot, * preserving invariant that objects with the same shape use the fixed * slots in the same way. */ size_t fixed = numFixedSlots(); if (shape->slot == fixed) { JS_ASSERT_IF(!lastProp->isEmptyShape() && lastProp->hasSlot(), lastProp->slot == fixed - 1); revertToFixedSlots(cx); } } updateShape(cx); /* On the way out, consider shrinking table if its load factor is <= .25. */ if (lastProp->hasTable()) { PropertyTable *table = lastProp->getTable(); uint32 size = table->capacity(); if (size > PropertyTable::MIN_SIZE && table->entryCount <= size >> 2) { METER(shrinks); (void) table->change(-1, cx); } } /* Also, consider shrinking object slots if 25% or more are unused. */ if (hasSlotsArray()) { JS_ASSERT(slotSpan() <= numSlots()); if ((slotSpan() + (slotSpan() >> 2)) < numSlots()) shrinkSlots(cx, slotSpan()); } CHECK_SHAPE_CONSISTENCY(this); METER(removes); return true; } void JSObject::clear(JSContext *cx) { Shape *shape = lastProp; JS_ASSERT(inDictionaryMode() == shape->inDictionary()); while (shape->parent) { shape = shape->parent; JS_ASSERT(inDictionaryMode() == shape->inDictionary()); } JS_ASSERT(shape->isEmptyShape()); if (inDictionaryMode()) shape->listp = &lastProp; /* * Revert to fixed slots if we have cleared below the first dynamically * allocated slot, preserving invariant that objects with the same shape * use the fixed slots in the same way. */ if (hasSlotsArray() && JSSLOT_FREE(getClass()) <= numFixedSlots()) revertToFixedSlots(cx); /* * We have rewound to a uniquely-shaped empty scope, so we don't need an * override for this object's shape. */ clearOwnShape(); setMap(shape); LeaveTraceIfGlobalObject(cx, this); JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals); CHECK_SHAPE_CONSISTENCY(this); } void JSObject::generateOwnShape(JSContext *cx) { #ifdef JS_TRACER JS_ASSERT_IF(!parent && JS_ON_TRACE(cx), JS_TRACE_MONITOR_ON_TRACE(cx)->bailExit); LeaveTraceIfGlobalObject(cx, this); /* * If we are recording, here is where we forget already-guarded shapes. * Any subsequent property operation upon object on the trace currently * being recorded will re-guard (and re-memoize). */ if (TraceRecorder *tr = TRACE_RECORDER(cx)) tr->forgetGuardedShapesForObject(this); #endif setOwnShape(js_GenerateShape(cx)); } void JSObject::deletingShapeChange(JSContext *cx, const Shape &shape) { JS_ASSERT(!JSID_IS_VOID(shape.propid)); generateOwnShape(cx); } const Shape * JSObject::methodShapeChange(JSContext *cx, const Shape &shape) { const Shape *result = &shape; JS_ASSERT(!JSID_IS_VOID(shape.propid)); if (shape.isMethod()) { #ifdef DEBUG const Value &prev = nativeGetSlot(shape.slot); JS_ASSERT(shape.methodObject() == prev.toObject()); JS_ASSERT(canHaveMethodBarrier()); JS_ASSERT(hasMethodBarrier()); JS_ASSERT(!shape.rawSetter || shape.rawSetter == js_watch_set); #endif /* * Pass null to make a stub getter, but pass along shape.rawSetter to * preserve watchpoints. Clear Shape::METHOD from flags as we are * despecializing from a method memoized in the property tree to a * plain old function-valued property. */ result = putProperty(cx, shape.propid, NULL, shape.rawSetter, shape.slot, shape.attrs, shape.getFlags() & ~Shape::METHOD, shape.shortid); if (!result) return NULL; } if (branded()) { uintN thrashCount = getMethodThrashCount(); if (thrashCount < JSObject::METHOD_THRASH_COUNT_MAX) { ++thrashCount; setMethodThrashCount(thrashCount); if (thrashCount == JSObject::METHOD_THRASH_COUNT_MAX) { unbrand(cx); return result; } } } generateOwnShape(cx); return result; } bool JSObject::methodShapeChange(JSContext *cx, uint32 slot) { if (!hasMethodBarrier()) { generateOwnShape(cx); } else { for (Shape::Range r = lastProp->all(); !r.empty(); r.popFront()) { const Shape &shape = r.front(); JS_ASSERT(!JSID_IS_VOID(shape.propid)); if (shape.slot == slot) return methodShapeChange(cx, shape) != NULL; } } return true; } void JSObject::protoShapeChange(JSContext *cx) { generateOwnShape(cx); } void JSObject::shadowingShapeChange(JSContext *cx, const Shape &shape) { JS_ASSERT(!JSID_IS_VOID(shape.propid)); generateOwnShape(cx); } bool JSObject::globalObjectOwnShapeChange(JSContext *cx) { generateOwnShape(cx); return !js_IsPropertyCacheDisabled(cx); }