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
1487 JSRuntime
*rt
= trc
->context
->runtime
;
1488 JS_ASSERT_IF(rt
->gcCurrentCompartment
, compartment
== rt
->gcCurrentCompartment
);
1491 if (IS_GC_MARKING_TRACER(trc
))
1494 MarkId(trc
, id
, "id");
1496 if (attrs
& (JSPROP_GETTER
| JSPROP_SETTER
)) {
1497 if ((attrs
& JSPROP_GETTER
) && rawGetter
) {
1498 JS_SET_TRACING_DETAILS(trc
, PrintPropertyGetterOrSetter
, this, 0);
1499 Mark(trc
, getterObject());
1501 if ((attrs
& JSPROP_SETTER
) && rawSetter
) {
1502 JS_SET_TRACING_DETAILS(trc
, PrintPropertyGetterOrSetter
, this, 1);
1503 Mark(trc
, setterObject());
1508 JS_SET_TRACING_DETAILS(trc
, PrintPropertyMethod
, this, 0);
1509 Mark(trc
, &methodObject());