1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sw=4 et tw=78:
3 : *
4 : * ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
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/
11 : *
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
15 : * License.
16 : *
17 : * The Original Code is Mozilla Communicator client code, released
18 : * March 31, 1998.
19 : *
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.
24 : *
25 : * Contributor(s):
26 : *
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.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : /*
42 : * JS symbol tables.
43 : */
44 : #include <new>
45 : #include <stdlib.h>
46 : #include <string.h>
47 : #include "jstypes.h"
48 : #include "jsclist.h"
49 : #include "jsdhash.h"
50 : #include "jsutil.h"
51 : #include "jsapi.h"
52 : #include "jsatom.h"
53 : #include "jscntxt.h"
54 : #include "jsdbgapi.h"
55 : #include "jslock.h"
56 : #include "jsnum.h"
57 : #include "jsobj.h"
58 : #include "jsscope.h"
59 : #include "jsstr.h"
60 :
61 : #include "js/MemoryMetrics.h"
62 :
63 : #include "jsatominlines.h"
64 : #include "jsobjinlines.h"
65 : #include "jsscopeinlines.h"
66 :
67 : using namespace js;
68 : using namespace js::gc;
69 :
70 : bool
71 351675 : PropertyTable::init(JSRuntime *rt, Shape *lastProp)
72 : {
73 : /*
74 : * Either we're creating a table for a large scope that was populated
75 : * via property cache hit logic under JSOP_INITPROP, JSOP_SETNAME, or
76 : * JSOP_SETPROP; or else calloc failed at least once already. In any
77 : * event, let's try to grow, overallocating to hold at least twice the
78 : * current population.
79 : */
80 351675 : uint32_t sizeLog2 = JS_CEILING_LOG2W(2 * entryCount);
81 351675 : if (sizeLog2 < MIN_SIZE_LOG2)
82 22596 : sizeLog2 = MIN_SIZE_LOG2;
83 :
84 : /*
85 : * Use rt->calloc_ for memory accounting and overpressure handling
86 : * without OOM reporting. See PropertyTable::change.
87 : */
88 351675 : entries = (Shape **) rt->calloc_(sizeOfEntries(JS_BIT(sizeLog2)));
89 351675 : if (!entries)
90 0 : return false;
91 :
92 351675 : hashShift = JS_DHASH_BITS - sizeLog2;
93 7104046 : for (Shape::Range r = lastProp->all(); !r.empty(); r.popFront()) {
94 6752371 : const Shape &shape = r.front();
95 6752371 : Shape **spp = search(shape.propid(), true);
96 :
97 : /*
98 : * Beware duplicate args and arg vs. var conflicts: the youngest shape
99 : * (nearest to lastProp) must win. See bug 600067.
100 : */
101 6752371 : if (!SHAPE_FETCH(spp))
102 6752371 : SHAPE_STORE_PRESERVING_COLLISION(spp, &shape);
103 : }
104 351675 : return true;
105 : }
106 :
107 : bool
108 351675 : Shape::makeOwnBaseShape(JSContext *cx)
109 : {
110 351675 : JS_ASSERT(!base()->isOwned());
111 :
112 703350 : RootedVarShape self(cx, this);
113 :
114 351675 : BaseShape *nbase = js_NewGCBaseShape(cx);
115 351675 : if (!nbase)
116 0 : return false;
117 :
118 351675 : new (nbase) BaseShape(*self->base());
119 351675 : nbase->setOwned(self->base()->toUnowned());
120 :
121 351675 : self->base_ = nbase;
122 :
123 351675 : return true;
124 : }
125 :
126 : void
127 5004595 : Shape::handoffTableTo(Shape *shape)
128 : {
129 5004595 : JS_ASSERT(inDictionary() && shape->inDictionary());
130 :
131 5004595 : if (this == shape)
132 287809 : return;
133 :
134 4716786 : JS_ASSERT(base()->isOwned() && !shape->base()->isOwned());
135 :
136 4716786 : BaseShape *nbase = base();
137 :
138 4716786 : JS_ASSERT_IF(shape->hasSlot(), nbase->slotSpan() > shape->slot());
139 :
140 4716786 : this->base_ = nbase->baseUnowned();
141 4716786 : nbase->adoptUnowned(shape->base()->toUnowned());
142 :
143 4716786 : shape->base_ = nbase;
144 : }
145 :
146 : bool
147 351675 : Shape::hashify(JSContext *cx)
148 : {
149 351675 : JS_ASSERT(!hasTable());
150 :
151 703350 : RootedVarShape self(cx, this);
152 :
153 351675 : if (!ensureOwnBaseShape(cx))
154 0 : return false;
155 :
156 351675 : JSRuntime *rt = cx->runtime;
157 351675 : PropertyTable *table = rt->new_<PropertyTable>(self->entryCount());
158 351675 : if (!table)
159 0 : return false;
160 :
161 351675 : if (!table->init(rt, self)) {
162 0 : rt->free_(table);
163 0 : return false;
164 : }
165 :
166 351675 : self->base()->setTable(table);
167 351675 : return true;
168 : }
169 :
170 : /*
171 : * Double hashing needs the second hash code to be relatively prime to table
172 : * size, so we simply make hash2 odd.
173 : */
174 : #define HASH1(hash0,shift) ((hash0) >> (shift))
175 : #define HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1)
176 :
177 : Shape **
178 146489419 : PropertyTable::search(jsid id, bool adding)
179 : {
180 : JSHashNumber hash0, hash1, hash2;
181 : int sizeLog2;
182 : Shape *stored, *shape, **spp, **firstRemoved;
183 : uint32_t sizeMask;
184 :
185 146489419 : JS_ASSERT(entries);
186 146489419 : JS_ASSERT(!JSID_IS_EMPTY(id));
187 :
188 : /* Compute the primary hash address. */
189 146489419 : hash0 = HashId(id);
190 146489419 : hash1 = HASH1(hash0, hashShift);
191 146489419 : spp = entries + hash1;
192 :
193 : /* Miss: return space for a new entry. */
194 146489419 : stored = *spp;
195 146489419 : if (SHAPE_IS_FREE(stored))
196 27373845 : return spp;
197 :
198 : /* Hit: return entry. */
199 119115574 : shape = SHAPE_CLEAR_COLLISION(stored);
200 119115574 : if (shape && shape->propid() == id)
201 79061880 : return spp;
202 :
203 : /* Collision: double hash. */
204 40053694 : sizeLog2 = JS_DHASH_BITS - hashShift;
205 40053694 : hash2 = HASH2(hash0, sizeLog2, hashShift);
206 40053694 : sizeMask = JS_BITMASK(sizeLog2);
207 :
208 : #ifdef DEBUG
209 40053694 : uintptr_t collision_flag = SHAPE_COLLISION;
210 : #endif
211 :
212 : /* Save the first removed entry pointer so we can recycle it if adding. */
213 40053694 : if (SHAPE_IS_REMOVED(stored)) {
214 361655 : firstRemoved = spp;
215 : } else {
216 39692039 : firstRemoved = NULL;
217 39692039 : if (adding && !SHAPE_HAD_COLLISION(stored))
218 2464486 : SHAPE_FLAG_COLLISION(spp, shape);
219 : #ifdef DEBUG
220 39692039 : collision_flag &= uintptr_t(*spp) & SHAPE_COLLISION;
221 : #endif
222 : }
223 :
224 31881217 : for (;;) {
225 71934911 : hash1 -= hash2;
226 71934911 : hash1 &= sizeMask;
227 71934911 : spp = entries + hash1;
228 :
229 71934911 : stored = *spp;
230 71934911 : if (SHAPE_IS_FREE(stored))
231 13832412 : return (adding && firstRemoved) ? firstRemoved : spp;
232 :
233 58102499 : shape = SHAPE_CLEAR_COLLISION(stored);
234 58102499 : if (shape && shape->propid() == id) {
235 26221282 : JS_ASSERT(collision_flag);
236 26221282 : return spp;
237 : }
238 :
239 31881217 : if (SHAPE_IS_REMOVED(stored)) {
240 246179 : if (!firstRemoved)
241 149668 : firstRemoved = spp;
242 : } else {
243 31635038 : if (adding && !SHAPE_HAD_COLLISION(stored))
244 1822187 : SHAPE_FLAG_COLLISION(spp, shape);
245 : #ifdef DEBUG
246 31635038 : collision_flag &= uintptr_t(*spp) & SHAPE_COLLISION;
247 : #endif
248 : }
249 : }
250 :
251 : /* NOTREACHED */
252 : return NULL;
253 : }
254 :
255 : bool
256 34849 : PropertyTable::change(int log2Delta, JSContext *cx)
257 : {
258 34849 : JS_ASSERT(entries);
259 :
260 : /*
261 : * Grow, shrink, or compress by changing this->entries.
262 : */
263 34849 : int oldlog2 = JS_DHASH_BITS - hashShift;
264 34849 : int newlog2 = oldlog2 + log2Delta;
265 34849 : uint32_t oldsize = JS_BIT(oldlog2);
266 34849 : uint32_t newsize = JS_BIT(newlog2);
267 34849 : Shape **newTable = (Shape **) cx->calloc_(sizeOfEntries(newsize));
268 34849 : if (!newTable)
269 0 : return false;
270 :
271 : /* Now that we have newTable allocated, update members. */
272 34849 : hashShift = JS_DHASH_BITS - newlog2;
273 34849 : removedCount = 0;
274 34849 : Shape **oldTable = entries;
275 34849 : entries = newTable;
276 :
277 : /* Copy only live entries, leaving removed and free ones behind. */
278 6628513 : for (Shape **oldspp = oldTable; oldsize != 0; oldspp++) {
279 6593664 : Shape *shape = SHAPE_FETCH(oldspp);
280 6593664 : if (shape) {
281 4427901 : Shape **spp = search(shape->propid(), true);
282 4427901 : JS_ASSERT(SHAPE_IS_FREE(*spp));
283 4427901 : *spp = shape;
284 : }
285 6593664 : oldsize--;
286 : }
287 :
288 : /* Finally, free the old entries storage. */
289 34849 : cx->free_(oldTable);
290 34849 : return true;
291 : }
292 :
293 : bool
294 23531 : PropertyTable::grow(JSContext *cx)
295 : {
296 23531 : JS_ASSERT(needsToGrow());
297 :
298 23531 : uint32_t size = capacity();
299 23531 : int delta = removedCount < size >> 2;
300 :
301 23531 : if (!change(delta, cx) && entryCount + removedCount == size - 1) {
302 0 : JS_ReportOutOfMemory(cx);
303 0 : return false;
304 : }
305 23531 : return true;
306 : }
307 :
308 : Shape *
309 2573523 : Shape::getChildBinding(JSContext *cx, const StackShape &child)
310 : {
311 2573523 : JS_ASSERT(!inDictionary());
312 :
313 2573523 : Shape *shape = JS_PROPERTY_TREE(cx).getChild(cx, this, numFixedSlots(), child);
314 2573523 : if (shape) {
315 2573523 : JS_ASSERT(shape->parent == this);
316 :
317 : /*
318 : * Update the number of fixed slots which bindings of this shape will
319 : * have. Bindings are constructed as new properties come in, so the
320 : * call object allocation class is not known ahead of time. Compute
321 : * the fixed slot count here, which will feed into call objects created
322 : * off of the bindings.
323 : */
324 2573523 : uint32_t slots = child.slotSpan() + 1; /* Add one for private data. */
325 2573523 : gc::AllocKind kind = gc::GetGCObjectKind(slots);
326 :
327 : /*
328 : * Make sure that the arguments and variables in the call object all
329 : * end up in a contiguous range of slots. We need this to be able to
330 : * embed the args/vars arrays in the TypeScriptNesting for the function
331 : * after the call object's frame has finished.
332 : */
333 2573523 : uint32_t nfixed = gc::GetGCKindSlots(kind);
334 2573523 : if (nfixed < slots) {
335 250937 : nfixed = CallObject::RESERVED_SLOTS + 1;
336 250937 : JS_ASSERT(gc::GetGCKindSlots(gc::GetGCObjectKind(nfixed)) == CallObject::RESERVED_SLOTS + 1);
337 : }
338 :
339 2573523 : shape->setNumFixedSlots(nfixed - 1);
340 : }
341 2573523 : return shape;
342 : }
343 :
344 : /* static */ Shape *
345 5355055 : Shape::replaceLastProperty(JSContext *cx, const StackBaseShape &base, JSObject *proto, Shape *shape)
346 : {
347 5355055 : JS_ASSERT(!shape->inDictionary());
348 :
349 5355055 : if (!shape->parent) {
350 : /* Treat as resetting the initial property of the shape hierarchy. */
351 4039299 : AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
352 : return EmptyShape::getInitialShape(cx, base.clasp, proto,
353 : base.parent, kind,
354 4039299 : base.flags & BaseShape::OBJECT_FLAG_MASK);
355 : }
356 :
357 2631512 : RootShape root(cx, &shape);
358 :
359 1315756 : UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
360 1315756 : if (!nbase)
361 0 : return NULL;
362 :
363 1315756 : StackShape child(shape);
364 1315756 : child.base = nbase;
365 :
366 1315756 : return JS_PROPERTY_TREE(cx).getChild(cx, shape->parent, shape->numFixedSlots(), child);
367 : }
368 :
369 : /*
370 : * Get or create a property-tree or dictionary child property of |parent|,
371 : * which must be lastProperty() if inDictionaryMode(), else parent must be
372 : * one of lastProperty() or lastProperty()->parent.
373 : */
374 : Shape *
375 41082663 : JSObject::getChildProperty(JSContext *cx, Shape *parent, StackShape &child)
376 : {
377 : /*
378 : * Shared properties have no slot, but slot_ will reflect that of parent.
379 : * Unshared properties allocate a slot here but may lose it due to a
380 : * JS_ClearScope call.
381 : */
382 41082663 : if (!child.hasSlot()) {
383 13071096 : child.setSlot(parent->maybeSlot());
384 : } else {
385 28011567 : if (child.hasMissingSlot()) {
386 : uint32_t slot;
387 25306732 : if (!allocSlot(cx, &slot))
388 0 : return NULL;
389 25306732 : child.setSlot(slot);
390 : } else {
391 : /* Slots can only be allocated out of order on objects in dictionary mode. */
392 5101891 : JS_ASSERT(inDictionaryMode() ||
393 : parent->hasMissingSlot() ||
394 5101891 : child.slot() == parent->maybeSlot() + 1);
395 : }
396 : }
397 :
398 : Shape *shape;
399 :
400 82165326 : RootedVarObject self(cx, this);
401 :
402 41082663 : if (inDictionaryMode()) {
403 3597360 : JS_ASSERT(parent == lastProperty());
404 7194720 : RootStackShape childRoot(cx, &child);
405 3597360 : shape = js_NewGCShape(cx);
406 3597360 : if (!shape)
407 0 : return NULL;
408 3597360 : if (child.hasSlot() && child.slot() >= self->lastProperty()->base()->slotSpan()) {
409 1067788 : if (!self->setSlotSpan(cx, child.slot() + 1))
410 0 : return NULL;
411 : }
412 7194720 : shape->initDictionaryShape(child, self->numFixedSlots(), &self->shape_);
413 : } else {
414 37485303 : shape = JS_PROPERTY_TREE(cx).getChild(cx, parent, self->numFixedSlots(), child);
415 37485303 : if (!shape)
416 0 : return NULL;
417 : //JS_ASSERT(shape->parent == parent);
418 : //JS_ASSERT_IF(parent != lastProperty(), parent == lastProperty()->parent);
419 37485303 : if (!self->setLastProperty(cx, shape))
420 0 : return NULL;
421 : }
422 :
423 41082663 : return shape;
424 : }
425 :
426 : bool
427 167286 : JSObject::toDictionaryMode(JSContext *cx)
428 : {
429 167286 : JS_ASSERT(!inDictionaryMode());
430 :
431 : /* We allocate the shapes from cx->compartment, so make sure it's right. */
432 167286 : JS_ASSERT(compartment() == cx->compartment);
433 :
434 167286 : uint32_t span = slotSpan();
435 :
436 334572 : RootedVarObject self(cx, this);
437 :
438 : /*
439 : * Clone the shapes into a new dictionary list. Don't update the
440 : * last property of this object until done, otherwise a GC
441 : * triggered while creating the dictionary will get the wrong
442 : * slot span for this object.
443 : */
444 334572 : RootedVarShape root(cx);
445 334572 : RootedVarShape dictionaryShape(cx);
446 :
447 334572 : RootedVarShape shape(cx);
448 167286 : shape = lastProperty();
449 :
450 4410969 : while (shape) {
451 4076397 : JS_ASSERT(!shape->inDictionary());
452 :
453 4076397 : Shape *dprop = js_NewGCShape(cx);
454 4076397 : if (!dprop) {
455 0 : js_ReportOutOfMemory(cx);
456 0 : return false;
457 : }
458 :
459 : HeapPtrShape *listp = dictionaryShape
460 3909111 : ? &dictionaryShape->parent
461 7985508 : : (HeapPtrShape *) root.address();
462 :
463 4076397 : StackShape child(shape);
464 4076397 : dprop->initDictionaryShape(child, self->numFixedSlots(), listp);
465 :
466 4076397 : JS_ASSERT(!dprop->hasTable());
467 4076397 : dictionaryShape = dprop;
468 4076397 : shape = shape->previous();
469 : }
470 :
471 167286 : if (!root->hashify(cx)) {
472 0 : js_ReportOutOfMemory(cx);
473 0 : return false;
474 : }
475 :
476 167286 : JS_ASSERT((Shape **) root->listp == root.address());
477 167286 : root->listp = &self->shape_;
478 167286 : self->shape_ = root;
479 :
480 167286 : JS_ASSERT(self->inDictionaryMode());
481 167286 : root->base()->setSlotSpan(span);
482 :
483 167286 : return true;
484 : }
485 :
486 : /*
487 : * Normalize stub getter and setter values for faster is-stub testing in the
488 : * SHAPE_CALL_[GS]ETTER macros.
489 : */
490 : static inline bool
491 42580218 : NormalizeGetterAndSetter(JSContext *cx, JSObject *obj,
492 : jsid id, unsigned attrs, unsigned flags,
493 : PropertyOp &getter,
494 : StrictPropertyOp &setter)
495 : {
496 42580218 : if (setter == JS_StrictPropertyStub) {
497 37889958 : JS_ASSERT(!(attrs & JSPROP_SETTER));
498 37889958 : setter = NULL;
499 : }
500 42580218 : if (flags & Shape::METHOD) {
501 0 : JS_ASSERT_IF(getter, getter == JS_PropertyStub);
502 0 : JS_ASSERT(!setter);
503 0 : JS_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
504 0 : getter = NULL;
505 : } else {
506 42580218 : if (getter == JS_PropertyStub) {
507 26872472 : JS_ASSERT(!(attrs & JSPROP_GETTER));
508 26872472 : getter = NULL;
509 : }
510 : }
511 :
512 42580218 : return true;
513 : }
514 :
515 : #ifdef DEBUG
516 : # define CHECK_SHAPE_CONSISTENCY(obj) obj->checkShapeConsistency()
517 :
518 : void
519 41756760 : JSObject::checkShapeConsistency()
520 : {
521 : static int throttle = -1;
522 41756760 : if (throttle < 0) {
523 19811 : if (const char *var = getenv("JS_CHECK_SHAPE_THROTTLE"))
524 0 : throttle = atoi(var);
525 19811 : if (throttle < 0)
526 19811 : throttle = 0;
527 : }
528 41756760 : if (throttle == 0)
529 41756760 : return;
530 :
531 0 : JS_ASSERT(isNative());
532 :
533 0 : Shape *shape = lastProperty();
534 0 : Shape *prev = NULL;
535 :
536 0 : if (inDictionaryMode()) {
537 0 : JS_ASSERT(shape->hasTable());
538 :
539 0 : PropertyTable &table = shape->table();
540 0 : for (uint32_t fslot = table.freelist; fslot != SHAPE_INVALID_SLOT;
541 0 : fslot = getSlot(fslot).toPrivateUint32()) {
542 0 : JS_ASSERT(fslot < slotSpan());
543 : }
544 :
545 0 : for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) {
546 0 : JS_ASSERT_IF(shape != lastProperty(), !shape->hasTable());
547 :
548 0 : Shape **spp = table.search(shape->propid(), false);
549 0 : JS_ASSERT(SHAPE_FETCH(spp) == shape);
550 : }
551 :
552 0 : shape = lastProperty();
553 0 : for (int n = throttle; --n >= 0 && shape; shape = shape->parent) {
554 0 : JS_ASSERT_IF(shape->slot() != SHAPE_INVALID_SLOT, shape->slot() < slotSpan());
555 0 : if (!prev) {
556 0 : JS_ASSERT(shape == lastProperty());
557 0 : JS_ASSERT(shape->listp == &shape_);
558 : } else {
559 0 : JS_ASSERT(shape->listp == &prev->parent);
560 : }
561 0 : prev = shape;
562 : }
563 : } else {
564 0 : for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) {
565 0 : if (shape->hasTable()) {
566 0 : PropertyTable &table = shape->table();
567 0 : JS_ASSERT(shape->parent);
568 0 : for (Shape::Range r(shape); !r.empty(); r.popFront()) {
569 0 : Shape **spp = table.search(r.front().propid(), false);
570 0 : JS_ASSERT(SHAPE_FETCH(spp) == &r.front());
571 : }
572 : }
573 0 : if (prev) {
574 0 : JS_ASSERT(prev->maybeSlot() >= shape->maybeSlot());
575 0 : shape->kids.checkConsistency(prev);
576 : }
577 0 : prev = shape;
578 : }
579 : }
580 : }
581 : #else
582 : # define CHECK_SHAPE_CONSISTENCY(obj) ((void)0)
583 : #endif
584 :
585 : Shape *
586 2073662 : JSObject::addProperty(JSContext *cx, jsid id,
587 : PropertyOp getter, StrictPropertyOp setter,
588 : uint32_t slot, unsigned attrs,
589 : unsigned flags, int shortid, bool allowDictionary)
590 : {
591 2073662 : JS_ASSERT(!JSID_IS_VOID(id));
592 :
593 2073662 : if (!isExtensible()) {
594 0 : reportNotExtensible(cx);
595 0 : return NULL;
596 : }
597 :
598 2073662 : NormalizeGetterAndSetter(cx, this, id, attrs, flags, getter, setter);
599 :
600 2073662 : Shape **spp = NULL;
601 2073662 : if (inDictionaryMode())
602 1147873 : spp = lastProperty()->table().search(id, true);
603 :
604 : return addPropertyInternal(cx, id, getter, setter, slot, attrs, flags, shortid,
605 2073662 : spp, allowDictionary);
606 : }
607 :
608 : Shape *
609 41063073 : JSObject::addPropertyInternal(JSContext *cx, jsid id,
610 : PropertyOp getter, StrictPropertyOp setter,
611 : uint32_t slot, unsigned attrs,
612 : unsigned flags, int shortid, Shape **spp,
613 : bool allowDictionary)
614 : {
615 41063073 : JS_ASSERT_IF(!allowDictionary, !inDictionaryMode());
616 :
617 82126146 : RootId idRoot(cx, &id);
618 82126146 : RootedVarObject self(cx, this);
619 :
620 41063073 : PropertyTable *table = NULL;
621 41063073 : if (!inDictionaryMode()) {
622 : bool stableSlot =
623 : (slot == SHAPE_INVALID_SLOT) ||
624 1556934 : lastProperty()->hasMissingSlot() ||
625 39060500 : (slot == lastProperty()->maybeSlot() + 1);
626 37503566 : JS_ASSERT_IF(!allowDictionary, stableSlot);
627 110691636 : if (allowDictionary &&
628 73188070 : (!stableSlot || lastProperty()->entryCount() >= PropertyTree::MAX_HEIGHT)) {
629 37853 : if (!toDictionaryMode(cx))
630 0 : return NULL;
631 37853 : table = &lastProperty()->table();
632 37853 : spp = table->search(id, true);
633 : }
634 : } else {
635 3559507 : table = &lastProperty()->table();
636 3559507 : if (table->needsToGrow()) {
637 23531 : if (!table->grow(cx))
638 0 : return NULL;
639 23531 : spp = table->search(id, true);
640 23531 : JS_ASSERT(!SHAPE_FETCH(spp));
641 : }
642 : }
643 :
644 41063073 : JS_ASSERT(!!table == !!spp);
645 :
646 : /* Find or create a property tree node labeled by our arguments. */
647 : Shape *shape;
648 : {
649 41063073 : shape = self->lastProperty();
650 :
651 : uint32_t index;
652 41063073 : bool indexed = js_IdIsIndex(id, &index);
653 : UnownedBaseShape *nbase;
654 41063073 : if (shape->base()->matchesGetterSetter(getter, setter) && !indexed) {
655 22586688 : nbase = shape->base()->unowned();
656 : } else {
657 18476385 : StackBaseShape base(shape->base());
658 18476385 : base.updateGetterSetter(attrs, getter, setter);
659 18476385 : if (indexed)
660 3870495 : base.flags |= BaseShape::INDEXED;
661 18476385 : nbase = BaseShape::getUnowned(cx, base);
662 18476385 : if (!nbase)
663 0 : return NULL;
664 : }
665 :
666 41063073 : StackShape child(nbase, id, slot, self->numFixedSlots(), attrs, flags, shortid);
667 41063073 : shape = self->getChildProperty(cx, self->lastProperty(), child);
668 : }
669 :
670 41063073 : if (shape) {
671 41063073 : JS_ASSERT(shape == self->lastProperty());
672 :
673 41063073 : if (table) {
674 : /* Store the tree node pointer in the table entry for id. */
675 3597360 : SHAPE_STORE_PRESERVING_COLLISION(spp, shape);
676 3597360 : ++table->entryCount;
677 :
678 : /* Pass the table along to the new last property, namely shape. */
679 3597360 : JS_ASSERT(&shape->parent->table() == table);
680 3597360 : shape->parent->handoffTableTo(shape);
681 : }
682 :
683 41063073 : CHECK_SHAPE_CONSISTENCY(self);
684 41063073 : return shape;
685 : }
686 :
687 0 : CHECK_SHAPE_CONSISTENCY(self);
688 0 : return NULL;
689 : }
690 :
691 : /*
692 : * Check and adjust the new attributes for the shape to make sure that our
693 : * slot access optimizations are sound. It is responsibility of the callers to
694 : * enforce all restrictions from ECMA-262 v5 8.12.9 [[DefineOwnProperty]].
695 : */
696 : inline bool
697 2479732 : CheckCanChangeAttrs(JSContext *cx, JSObject *obj, const Shape *shape, unsigned *attrsp)
698 : {
699 2479732 : if (shape->configurable())
700 2194289 : return true;
701 :
702 : /* A permanent property must stay permanent. */
703 285443 : *attrsp |= JSPROP_PERMANENT;
704 :
705 : /* Reject attempts to remove a slot from the permanent data property. */
706 285443 : if (shape->isDataDescriptor() && shape->hasSlot() &&
707 : (*attrsp & (JSPROP_GETTER | JSPROP_SETTER | JSPROP_SHARED))) {
708 0 : obj->reportNotConfigurable(cx, shape->propid());
709 0 : return false;
710 : }
711 :
712 285443 : return true;
713 : }
714 :
715 : Shape *
716 40506556 : JSObject::putProperty(JSContext *cx, jsid id,
717 : PropertyOp getter, StrictPropertyOp setter,
718 : uint32_t slot, unsigned attrs,
719 : unsigned flags, int shortid)
720 : {
721 40506556 : JS_ASSERT(!JSID_IS_VOID(id));
722 :
723 81013112 : RootId idRoot(cx, &id);
724 :
725 40506556 : NormalizeGetterAndSetter(cx, this, id, attrs, flags, getter, setter);
726 :
727 81013112 : RootedVarObject self(cx, this);
728 :
729 : /* Search for id in order to claim its entry if table has been allocated. */
730 : Shape **spp;
731 40506556 : Shape *shape = Shape::search(cx, lastProperty(), id, &spp, true);
732 40506556 : if (!shape) {
733 : /*
734 : * You can't add properties to a non-extensible object, but you can change
735 : * attributes of properties in such objects.
736 : */
737 38097111 : if (!self->isExtensible()) {
738 0 : self->reportNotExtensible(cx);
739 0 : return NULL;
740 : }
741 :
742 38097111 : return self->addPropertyInternal(cx, id, getter, setter, slot, attrs, flags, shortid, spp, true);
743 : }
744 :
745 : /* Property exists: search must have returned a valid *spp. */
746 2409445 : JS_ASSERT_IF(spp, !SHAPE_IS_REMOVED(*spp));
747 :
748 4818890 : RootShape shapeRoot(cx, &shape);
749 :
750 2409445 : if (!CheckCanChangeAttrs(cx, self, shape, &attrs))
751 0 : return NULL;
752 :
753 : /*
754 : * If the caller wants to allocate a slot, but doesn't care which slot,
755 : * copy the existing shape's slot into slot so we can match shape, if all
756 : * other members match.
757 : */
758 2409445 : bool hadSlot = shape->hasSlot();
759 2409445 : uint32_t oldSlot = shape->maybeSlot();
760 2409445 : if (!(attrs & JSPROP_SHARED) && slot == SHAPE_INVALID_SLOT && hadSlot)
761 2320495 : slot = oldSlot;
762 :
763 4818890 : RootedVar<UnownedBaseShape*> nbase(cx);
764 : {
765 : uint32_t index;
766 2409445 : bool indexed = js_IdIsIndex(id, &index);
767 2409445 : StackBaseShape base(self->lastProperty()->base());
768 2409445 : base.updateGetterSetter(attrs, getter, setter);
769 2409445 : if (indexed)
770 126 : base.flags |= BaseShape::INDEXED;
771 2409445 : nbase = BaseShape::getUnowned(cx, base);
772 2409445 : if (!nbase)
773 0 : return NULL;
774 : }
775 :
776 : /*
777 : * Now that we've possibly preserved slot, check whether all members match.
778 : * If so, this is a redundant "put" and we can return without more work.
779 : */
780 2409445 : if (shape->matchesParamsAfterId(nbase, slot, attrs, flags, shortid))
781 2271937 : return shape;
782 :
783 : /*
784 : * Overwriting a non-last property requires switching to dictionary mode.
785 : * The shape tree is shared immutable, and we can't removeProperty and then
786 : * addPropertyInternal because a failure under add would lose data.
787 : */
788 137508 : if (shape != self->lastProperty() && !self->inDictionaryMode()) {
789 50562 : if (!self->toDictionaryMode(cx))
790 0 : return NULL;
791 50562 : spp = self->lastProperty()->table().search(shape->propid(), false);
792 50562 : shape = SHAPE_FETCH(spp);
793 : }
794 :
795 137508 : JS_ASSERT_IF(shape->hasSlot() && !(attrs & JSPROP_SHARED), shape->slot() == slot);
796 :
797 137508 : if (self->inDictionaryMode()) {
798 : /*
799 : * Updating some property in a dictionary-mode object. Create a new
800 : * shape for the existing property, and also generate a new shape for
801 : * the last property of the dictionary (unless the modified property
802 : * is also the last property).
803 : */
804 117918 : bool updateLast = (shape == self->lastProperty());
805 117918 : shape = self->replaceWithNewEquivalentShape(cx, shape);
806 117918 : if (!shape)
807 0 : return NULL;
808 117918 : if (!updateLast && !self->generateOwnShape(cx))
809 0 : return NULL;
810 :
811 : /* FIXME bug 593129 -- slot allocation and JSObject *this must move out of here! */
812 117918 : if (slot == SHAPE_INVALID_SLOT && !(attrs & JSPROP_SHARED)) {
813 9 : if (!self->allocSlot(cx, &slot))
814 0 : return NULL;
815 : }
816 :
817 117918 : if (updateLast)
818 16799 : shape->base()->adoptUnowned(nbase);
819 : else
820 101119 : shape->base_ = nbase;
821 :
822 117918 : shape->setSlot(slot);
823 117918 : shape->attrs = uint8_t(attrs);
824 117918 : shape->flags = flags | Shape::IN_DICTIONARY;
825 117918 : shape->shortid_ = int16_t(shortid);
826 : } else {
827 : /*
828 : * Updating the last property in a non-dictionary-mode object. Find an
829 : * alternate shared child of the last property's previous shape.
830 : */
831 19590 : StackBaseShape base(self->lastProperty()->base());
832 19590 : base.updateGetterSetter(attrs, getter, setter);
833 19590 : UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
834 19590 : if (!nbase)
835 0 : return NULL;
836 :
837 19590 : JS_ASSERT(shape == self->lastProperty());
838 :
839 : /* Find or create a property tree node labeled by our arguments. */
840 19590 : StackShape child(nbase, id, slot, self->numFixedSlots(), attrs, flags, shortid);
841 19590 : Shape *newShape = self->getChildProperty(cx, shape->parent, child);
842 :
843 19590 : if (!newShape) {
844 0 : CHECK_SHAPE_CONSISTENCY(self);
845 0 : return NULL;
846 : }
847 :
848 19590 : shape = newShape;
849 : }
850 :
851 : /*
852 : * Can't fail now, so free the previous incarnation's slot if the new shape
853 : * has no slot. But we do not need to free oldSlot (and must not, as trying
854 : * to will botch an assertion in JSObject::freeSlot) if the new last
855 : * property (shape here) has a slotSpan that does not cover it.
856 : */
857 137508 : if (hadSlot && !shape->hasSlot()) {
858 348 : if (oldSlot < self->slotSpan())
859 294 : self->freeSlot(cx, oldSlot);
860 348 : JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals);
861 : }
862 :
863 137508 : CHECK_SHAPE_CONSISTENCY(self);
864 :
865 137508 : return shape;
866 : }
867 :
868 : Shape *
869 70287 : JSObject::changeProperty(JSContext *cx, Shape *shape, unsigned attrs, unsigned mask,
870 : PropertyOp getter, StrictPropertyOp setter)
871 : {
872 70287 : JS_ASSERT(nativeContains(cx, *shape));
873 :
874 70287 : attrs |= shape->attrs & mask;
875 :
876 : /* Allow only shared (slotless) => unshared (slotful) transition. */
877 70287 : JS_ASSERT(!((attrs ^ shape->attrs) & JSPROP_SHARED) ||
878 70287 : !(attrs & JSPROP_SHARED));
879 :
880 : /* Don't allow method properties to be changed to have a getter or setter. */
881 70287 : JS_ASSERT_IF(shape->isMethod(), !getter && !setter);
882 :
883 70287 : types::MarkTypePropertyConfigured(cx, this, shape->propid());
884 70287 : if (attrs & (JSPROP_GETTER | JSPROP_SETTER))
885 29662 : types::AddTypePropertyId(cx, this, shape->propid(), types::Type::UnknownType());
886 :
887 70287 : if (getter == JS_PropertyStub)
888 0 : getter = NULL;
889 70287 : if (setter == JS_StrictPropertyStub)
890 0 : setter = NULL;
891 :
892 70287 : if (!CheckCanChangeAttrs(cx, this, shape, &attrs))
893 0 : return NULL;
894 :
895 70287 : if (shape->attrs == attrs && shape->getter() == getter && shape->setter() == setter)
896 0 : return shape;
897 :
898 : /*
899 : * Let JSObject::putProperty handle this |overwriting| case, including
900 : * the conservation of shape->slot (if it's valid). We must not call
901 : * removeProperty because it will free an allocated shape->slot, and
902 : * putProperty won't re-allocate it.
903 : */
904 70287 : Shape *newShape = putProperty(cx, shape->propid(), getter, setter, shape->maybeSlot(),
905 140574 : attrs, shape->flags, shape->maybeShortid());
906 :
907 70287 : CHECK_SHAPE_CONSISTENCY(this);
908 70287 : return newShape;
909 : }
910 :
911 : bool
912 485882 : JSObject::removeProperty(JSContext *cx, jsid id)
913 : {
914 971764 : RootedVarObject self(cx, this);
915 :
916 971764 : RootId idRoot(cx, &id);
917 971764 : RootedVarShape shape(cx);
918 :
919 : Shape **spp;
920 485882 : shape = Shape::search(cx, lastProperty(), id, &spp);
921 485882 : if (!shape)
922 0 : return true;
923 :
924 : /*
925 : * If shape is not the last property added, or the last property cannot
926 : * be removed, switch to dictionary mode.
927 : */
928 485882 : if (!self->inDictionaryMode() && (shape != self->lastProperty() || !self->canRemoveLastProperty())) {
929 9499 : if (!self->toDictionaryMode(cx))
930 0 : return false;
931 9499 : spp = self->lastProperty()->table().search(shape->propid(), false);
932 9499 : shape = SHAPE_FETCH(spp);
933 : }
934 :
935 : /*
936 : * If in dictionary mode, get a new shape for the last property after the
937 : * removal. We need a fresh shape for all dictionary deletions, even of
938 : * the last property. Otherwise, a shape could replay and caches might
939 : * return deleted DictionaryShapes! See bug 595365. Do this before changing
940 : * the object or table, so the remaining removal is infallible.
941 : */
942 485882 : Shape *spare = NULL;
943 485882 : if (self->inDictionaryMode()) {
944 481706 : spare = js_NewGCShape(cx);
945 481706 : if (!spare)
946 0 : return false;
947 481706 : new (spare) Shape(shape->base()->unowned(), 0);
948 481706 : if (shape == lastProperty()) {
949 : /*
950 : * Get an up to date unowned base shape for the new last property
951 : * when removing the dictionary's last property. Information in
952 : * base shapes for non-last properties may be out of sync with the
953 : * object's state.
954 : */
955 193897 : Shape *previous = lastProperty()->parent;
956 193897 : StackBaseShape base(lastProperty()->base());
957 193897 : base.updateGetterSetter(previous->attrs, previous->getter(), previous->setter());
958 193897 : BaseShape *nbase = BaseShape::getUnowned(cx, base);
959 193897 : if (!nbase)
960 0 : return false;
961 193897 : previous->base_ = nbase;
962 : }
963 : }
964 :
965 : /* If shape has a slot, free its slot number. */
966 485882 : if (shape->hasSlot()) {
967 447843 : self->freeSlot(cx, shape->slot());
968 447843 : JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals);
969 : }
970 :
971 : /*
972 : * A dictionary-mode object owns mutable, unique shapes on a non-circular
973 : * doubly linked list, hashed by lastProperty()->table. So we can edit the
974 : * list and hash in place.
975 : */
976 485882 : if (self->inDictionaryMode()) {
977 481706 : PropertyTable &table = self->lastProperty()->table();
978 :
979 481706 : if (SHAPE_HAD_COLLISION(*spp)) {
980 149983 : *spp = SHAPE_REMOVED;
981 149983 : ++table.removedCount;
982 149983 : --table.entryCount;
983 : } else {
984 331723 : *spp = NULL;
985 331723 : --table.entryCount;
986 :
987 : #ifdef DEBUG
988 : /*
989 : * Check the consistency of the table but limit the number of
990 : * checks not to alter significantly the complexity of the
991 : * delete in debug builds, see bug 534493.
992 : */
993 331723 : const Shape *aprop = self->lastProperty();
994 13268528 : for (int n = 50; --n >= 0 && aprop->parent; aprop = aprop->parent)
995 12936805 : JS_ASSERT_IF(aprop != shape, self->nativeContains(cx, *aprop));
996 : #endif
997 : }
998 :
999 : /* Remove shape from its non-circular doubly linked list. */
1000 481706 : Shape *oldLastProp = self->lastProperty();
1001 481706 : shape->removeFromDictionary(self);
1002 :
1003 : /* Hand off table from the old to new last property. */
1004 481706 : oldLastProp->handoffTableTo(self->lastProperty());
1005 :
1006 : /* Generate a new shape for the object, infallibly. */
1007 481706 : JS_ALWAYS_TRUE(self->generateOwnShape(cx, spare));
1008 :
1009 : /* Consider shrinking table if its load factor is <= .25. */
1010 481706 : uint32_t size = table.capacity();
1011 481706 : if (size > PropertyTable::MIN_SIZE && table.entryCount <= size >> 2)
1012 11318 : (void) table.change(-1, cx);
1013 : } else {
1014 : /*
1015 : * Non-dictionary-mode property tables are shared immutables, so all we
1016 : * need do is retract the last property and we'll either get or else
1017 : * lazily make via a later hashify the exact table for the new property
1018 : * lineage.
1019 : */
1020 4176 : JS_ASSERT(shape == self->lastProperty());
1021 4176 : self->removeLastProperty(cx);
1022 : }
1023 :
1024 485882 : CHECK_SHAPE_CONSISTENCY(self);
1025 485882 : return true;
1026 : }
1027 :
1028 : void
1029 10 : JSObject::clear(JSContext *cx)
1030 : {
1031 10 : Shape *shape = lastProperty();
1032 10 : JS_ASSERT(inDictionaryMode() == shape->inDictionary());
1033 :
1034 20 : while (shape->parent) {
1035 0 : shape = shape->parent;
1036 0 : JS_ASSERT(inDictionaryMode() == shape->inDictionary());
1037 : }
1038 10 : JS_ASSERT(shape->isEmptyShape());
1039 :
1040 10 : if (inDictionaryMode())
1041 0 : shape->listp = &shape_;
1042 :
1043 10 : JS_ALWAYS_TRUE(setLastProperty(cx, shape));
1044 :
1045 10 : JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals);
1046 10 : CHECK_SHAPE_CONSISTENCY(this);
1047 10 : }
1048 :
1049 : void
1050 20 : JSObject::rollbackProperties(JSContext *cx, uint32_t slotSpan)
1051 : {
1052 : /*
1053 : * Remove properties from this object until it has a matching slot span.
1054 : * The object cannot have escaped in a way which would prevent safe
1055 : * removal of the last properties.
1056 : */
1057 20 : JS_ASSERT(!inDictionaryMode() && slotSpan <= this->slotSpan());
1058 85 : while (this->slotSpan() != slotSpan) {
1059 45 : JS_ASSERT(lastProperty()->hasSlot() && getSlot(lastProperty()->slot()).isUndefined());
1060 45 : removeLastProperty(cx);
1061 : }
1062 20 : }
1063 :
1064 : Shape *
1065 1026648 : JSObject::replaceWithNewEquivalentShape(JSContext *cx, Shape *oldShape, Shape *newShape)
1066 : {
1067 1228886 : JS_ASSERT_IF(oldShape != lastProperty(),
1068 : inDictionaryMode() &&
1069 1228886 : nativeLookup(cx, oldShape->propidRef()) == oldShape);
1070 :
1071 1026648 : JSObject *self = this;
1072 :
1073 1026648 : if (!inDictionaryMode()) {
1074 127768 : RootObject selfRoot(cx, &self);
1075 127768 : RootShape newRoot(cx, &newShape);
1076 63884 : if (!toDictionaryMode(cx))
1077 0 : return NULL;
1078 127768 : oldShape = lastProperty();
1079 : }
1080 :
1081 1026648 : if (!newShape) {
1082 1089884 : RootObject selfRoot(cx, &self);
1083 1089884 : RootShape oldRoot(cx, &oldShape);
1084 544942 : newShape = js_NewGCShape(cx);
1085 544942 : if (!newShape)
1086 0 : return NULL;
1087 544942 : new (newShape) Shape(oldShape->base()->unowned(), 0);
1088 : }
1089 :
1090 1026648 : PropertyTable &table = self->lastProperty()->table();
1091 1026648 : Shape **spp = oldShape->isEmptyShape()
1092 : ? NULL
1093 1026648 : : table.search(oldShape->propidRef(), false);
1094 :
1095 : /*
1096 : * Splice the new shape into the same position as the old shape, preserving
1097 : * enumeration order (see bug 601399).
1098 : */
1099 1026648 : StackShape nshape(oldShape);
1100 1026648 : newShape->initDictionaryShape(nshape, self->numFixedSlots(), oldShape->listp);
1101 :
1102 1026648 : JS_ASSERT(newShape->parent == oldShape);
1103 1026648 : oldShape->removeFromDictionary(self);
1104 :
1105 1026648 : if (newShape == lastProperty())
1106 925529 : oldShape->handoffTableTo(newShape);
1107 :
1108 1026648 : if (spp)
1109 1009981 : SHAPE_STORE_PRESERVING_COLLISION(spp, newShape);
1110 1026648 : return newShape;
1111 : }
1112 :
1113 : Shape *
1114 0 : JSObject::methodShapeChange(JSContext *cx, const Shape &shape)
1115 : {
1116 0 : JS_ASSERT(shape.isMethod());
1117 :
1118 0 : if (!inDictionaryMode() && !toDictionaryMode(cx))
1119 0 : return NULL;
1120 :
1121 0 : Shape *spare = js_NewGCShape(cx);
1122 0 : if (!spare)
1123 0 : return NULL;
1124 0 : new (spare) Shape(shape.base()->unowned(), 0);
1125 :
1126 : #ifdef DEBUG
1127 0 : JS_ASSERT(canHaveMethodBarrier());
1128 0 : JS_ASSERT(!shape.setter());
1129 0 : JS_ASSERT(!shape.hasShortID());
1130 : #endif
1131 :
1132 : /*
1133 : * Clear Shape::METHOD from flags as we are despecializing from a
1134 : * method memoized in the property tree to a plain old function-valued
1135 : * property.
1136 : */
1137 : Shape *result =
1138 0 : putProperty(cx, shape.propid(), NULL, NULL, shape.slot(),
1139 : shape.attrs,
1140 0 : shape.getFlags() & ~Shape::METHOD,
1141 0 : 0);
1142 0 : if (!result)
1143 0 : return NULL;
1144 :
1145 0 : if (result != lastProperty())
1146 0 : JS_ALWAYS_TRUE(generateOwnShape(cx, spare));
1147 :
1148 0 : return result;
1149 : }
1150 :
1151 : bool
1152 231984 : JSObject::shadowingShapeChange(JSContext *cx, const Shape &shape)
1153 : {
1154 231984 : return generateOwnShape(cx);
1155 : }
1156 :
1157 : bool
1158 1074117 : JSObject::clearParent(JSContext *cx)
1159 : {
1160 1074117 : return setParent(cx, NULL);
1161 : }
1162 :
1163 : bool
1164 1439594 : JSObject::setParent(JSContext *cx, JSObject *parent)
1165 : {
1166 1439594 : if (parent && !parent->setDelegate(cx))
1167 0 : return false;
1168 :
1169 1439594 : if (inDictionaryMode()) {
1170 0 : StackBaseShape base(lastProperty());
1171 0 : base.parent = parent;
1172 0 : UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
1173 0 : if (!nbase)
1174 0 : return false;
1175 :
1176 0 : lastProperty()->base()->adoptUnowned(nbase);
1177 0 : return true;
1178 : }
1179 :
1180 1439594 : Shape *newShape = Shape::setObjectParent(cx, parent, getProto(), shape_);
1181 1439594 : if (!newShape)
1182 0 : return false;
1183 :
1184 1439594 : shape_ = newShape;
1185 1439594 : return true;
1186 : }
1187 :
1188 : /* static */ Shape *
1189 1594787 : Shape::setObjectParent(JSContext *cx, JSObject *parent, JSObject *proto, Shape *last)
1190 : {
1191 1594787 : if (last->getObjectParent() == parent)
1192 69708 : return last;
1193 :
1194 1525079 : StackBaseShape base(last);
1195 1525079 : base.parent = parent;
1196 :
1197 1525079 : return replaceLastProperty(cx, base, proto, last);
1198 : }
1199 :
1200 : bool
1201 1261940 : JSObject::preventExtensions(JSContext *cx, js::AutoIdVector *props)
1202 : {
1203 1261940 : JS_ASSERT(isExtensible());
1204 :
1205 2523880 : RootedVarObject self(cx, this);
1206 :
1207 1261940 : if (props) {
1208 1261940 : if (js::FixOp fix = getOps()->fix) {
1209 : bool success;
1210 86 : if (!fix(cx, this, &success, props))
1211 0 : return false;
1212 86 : if (!success) {
1213 0 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CHANGE_EXTENSIBILITY);
1214 0 : return false;
1215 : }
1216 : } else {
1217 1261854 : if (!js::GetPropertyNames(cx, this, JSITER_HIDDEN | JSITER_OWNONLY, props))
1218 0 : return false;
1219 : }
1220 : }
1221 :
1222 1261940 : return self->setFlag(cx, BaseShape::NOT_EXTENSIBLE, GENERATE_SHAPE);
1223 : }
1224 :
1225 : bool
1226 7970313 : JSObject::setFlag(JSContext *cx, /*BaseShape::Flag*/ uint32_t flag_, GenerateShape generateShape)
1227 : {
1228 7970313 : BaseShape::Flag flag = (BaseShape::Flag) flag_;
1229 :
1230 7970313 : if (lastProperty()->getObjectFlags() & flag)
1231 4095856 : return true;
1232 :
1233 7748914 : RootedVarObject self(cx, this);
1234 :
1235 3874457 : if (inDictionaryMode()) {
1236 61426 : if (generateShape == GENERATE_SHAPE && !generateOwnShape(cx))
1237 0 : return false;
1238 61426 : StackBaseShape base(self->lastProperty());
1239 61426 : base.flags |= flag;
1240 61426 : UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
1241 61426 : if (!nbase)
1242 0 : return false;
1243 :
1244 61426 : self->lastProperty()->base()->adoptUnowned(nbase);
1245 61426 : return true;
1246 : }
1247 :
1248 3813031 : Shape *newShape = Shape::setObjectFlag(cx, flag, getProto(), lastProperty());
1249 3813031 : if (!newShape)
1250 0 : return false;
1251 :
1252 3813031 : self->shape_ = newShape;
1253 3813031 : return true;
1254 : }
1255 :
1256 : /* static */ Shape *
1257 3813031 : Shape::setObjectFlag(JSContext *cx, BaseShape::Flag flag, JSObject *proto, Shape *last)
1258 : {
1259 3813031 : if (last->getObjectFlags() & flag)
1260 0 : return last;
1261 :
1262 3813031 : StackBaseShape base(last);
1263 3813031 : base.flags |= flag;
1264 :
1265 3813031 : return replaceLastProperty(cx, base, proto, last);
1266 : }
1267 :
1268 : /* static */ inline HashNumber
1269 28762737 : StackBaseShape::hash(const StackBaseShape *base)
1270 : {
1271 28762737 : JSDHashNumber hash = base->flags;
1272 28762737 : hash = JS_ROTATE_LEFT32(hash, 4) ^ (uintptr_t(base->clasp) >> 3);
1273 28762737 : hash = JS_ROTATE_LEFT32(hash, 4) ^ (uintptr_t(base->parent) >> 3);
1274 28762737 : hash = JS_ROTATE_LEFT32(hash, 4) ^ uintptr_t(base->rawGetter);
1275 28762737 : hash = JS_ROTATE_LEFT32(hash, 4) ^ uintptr_t(base->rawSetter);
1276 28762737 : return hash;
1277 : }
1278 :
1279 : /* static */ inline bool
1280 21564424 : StackBaseShape::match(UnownedBaseShape *key, const StackBaseShape *lookup)
1281 : {
1282 : return key->flags == lookup->flags
1283 : && key->clasp == lookup->clasp
1284 21558846 : && key->parent == lookup->parent
1285 : && key->rawGetter == lookup->rawGetter
1286 43123270 : && key->rawSetter == lookup->rawSetter;
1287 : }
1288 :
1289 : /* Root for stack allocated base shapes. */
1290 : class RootStackBaseShape
1291 7215781 : {
1292 : Root<const JSObject*> parentRoot;
1293 : Maybe<RootObject> getterRoot;
1294 : Maybe<RootObject> setterRoot;
1295 :
1296 : public:
1297 7215781 : RootStackBaseShape(JSContext *cx, const StackBaseShape *base)
1298 7215781 : : parentRoot(cx, &base->parent)
1299 : {
1300 7215781 : if (base->flags & BaseShape::HAS_GETTER_OBJECT)
1301 2423342 : getterRoot.construct(cx, (JSObject **) &base->rawGetter);
1302 7215781 : if (base->flags & BaseShape::HAS_SETTER_OBJECT)
1303 438964 : setterRoot.construct(cx, (JSObject **) &base->rawSetter);
1304 7215781 : }
1305 : };
1306 :
1307 : /* static */ UnownedBaseShape *
1308 28762737 : BaseShape::getUnowned(JSContext *cx, const StackBaseShape &base)
1309 : {
1310 28762737 : BaseShapeSet &table = cx->compartment->baseShapes;
1311 :
1312 28762737 : if (!table.initialized() && !table.init())
1313 0 : return NULL;
1314 :
1315 28762737 : BaseShapeSet::AddPtr p = table.lookupForAdd(&base);
1316 :
1317 28762737 : if (p)
1318 21546956 : return *p;
1319 :
1320 14431562 : RootStackBaseShape root(cx, &base);
1321 :
1322 7215781 : BaseShape *nbase_ = js_NewGCBaseShape(cx);
1323 7215781 : if (!nbase_)
1324 0 : return NULL;
1325 7215781 : new (nbase_) BaseShape(base);
1326 :
1327 7215781 : UnownedBaseShape *nbase = static_cast<UnownedBaseShape *>(nbase_);
1328 :
1329 7215781 : if (!table.relookupOrAdd(p, &base, nbase))
1330 0 : return NULL;
1331 :
1332 7215781 : return nbase;
1333 : }
1334 :
1335 : void
1336 122478 : JSCompartment::sweepBaseShapeTable(JSContext *cx)
1337 : {
1338 122478 : if (baseShapes.initialized()) {
1339 21127213 : for (BaseShapeSet::Enum e(baseShapes); !e.empty(); e.popFront()) {
1340 21055611 : UnownedBaseShape *base = e.front();
1341 21055611 : if (!base->isMarked())
1342 7214926 : e.removeFront();
1343 : }
1344 : }
1345 122478 : }
1346 :
1347 : void
1348 7566440 : BaseShape::finalize(JSContext *cx, bool background)
1349 : {
1350 7566440 : if (table_) {
1351 351514 : cx->delete_(table_);
1352 351514 : table_ = NULL;
1353 : }
1354 7566440 : }
1355 :
1356 : /* static */ Shape *
1357 16945 : Shape::setExtensibleParents(JSContext *cx, Shape *shape)
1358 : {
1359 16945 : JS_ASSERT(!shape->inDictionary());
1360 :
1361 16945 : StackBaseShape base(shape);
1362 16945 : base.flags |= BaseShape::EXTENSIBLE_PARENTS;
1363 :
1364 : /* This is only used for Block and Call objects, which have a NULL proto. */
1365 16945 : return replaceLastProperty(cx, base, NULL, shape);
1366 : }
1367 :
1368 : bool
1369 8413 : Bindings::setExtensibleParents(JSContext *cx)
1370 : {
1371 8413 : if (!ensureShape(cx))
1372 0 : return false;
1373 8413 : Shape *newShape = Shape::setExtensibleParents(cx, lastBinding);
1374 8413 : if (!newShape)
1375 0 : return false;
1376 8413 : lastBinding = newShape;
1377 8413 : return true;
1378 : }
1379 :
1380 : bool
1381 155193 : Bindings::setParent(JSContext *cx, JSObject *obj)
1382 : {
1383 : /*
1384 : * This may be invoked on GC heap allocated bindings, in which case this
1385 : * is pointing to an internal value of a JSScript that can't itself be
1386 : * relocated. The script itself will be rooted, and will not be moved, so
1387 : * mark the stack value as non-relocatable for the stack root analysis.
1388 : */
1389 155193 : Bindings *self = this;
1390 310386 : CheckRoot root(cx, &self);
1391 :
1392 310386 : RootObject rootObj(cx, &obj);
1393 :
1394 155193 : if (!ensureShape(cx))
1395 0 : return false;
1396 :
1397 : /* This is only used for Block objects, which have a NULL proto. */
1398 155193 : Shape *newShape = Shape::setObjectParent(cx, obj, NULL, self->lastBinding);
1399 155193 : if (!newShape)
1400 0 : return false;
1401 155193 : self->lastBinding = newShape;
1402 155193 : return true;
1403 : }
1404 :
1405 : /* static */ inline HashNumber
1406 17387944 : InitialShapeEntry::hash(const Lookup &lookup)
1407 : {
1408 17387944 : JSDHashNumber hash = uintptr_t(lookup.clasp) >> 3;
1409 17387944 : hash = JS_ROTATE_LEFT32(hash, 4) ^ (uintptr_t(lookup.proto) >> 3);
1410 17387944 : hash = JS_ROTATE_LEFT32(hash, 4) ^ (uintptr_t(lookup.parent) >> 3);
1411 17387944 : return hash + lookup.nfixed;
1412 : }
1413 :
1414 : /* static */ inline bool
1415 20279258 : InitialShapeEntry::match(const InitialShapeEntry &key, const Lookup &lookup)
1416 : {
1417 20279258 : return lookup.clasp == key.shape->getObjectClass()
1418 : && lookup.proto == key.proto
1419 20275989 : && lookup.parent == key.shape->getObjectParent()
1420 20275989 : && lookup.nfixed == key.shape->numFixedSlots()
1421 81110494 : && lookup.baseFlags == key.shape->getObjectFlags();
1422 : }
1423 :
1424 : /* static */ Shape *
1425 17352641 : EmptyShape::getInitialShape(JSContext *cx, Class *clasp, JSObject *proto, JSObject *parent,
1426 : AllocKind kind, uint32_t objectFlags)
1427 : {
1428 17352641 : InitialShapeSet &table = cx->compartment->initialShapes;
1429 :
1430 17352641 : if (!table.initialized() && !table.init())
1431 0 : return NULL;
1432 :
1433 17352641 : size_t nfixed = GetGCKindSlots(kind, clasp);
1434 17352641 : InitialShapeEntry::Lookup lookup(clasp, proto, parent, nfixed, objectFlags);
1435 :
1436 17352641 : InitialShapeSet::AddPtr p = table.lookupForAdd(lookup);
1437 :
1438 17352641 : if (p)
1439 13639917 : return p->shape;
1440 :
1441 7425448 : RootedVar<UnownedBaseShape*> nbase(cx);
1442 :
1443 3712724 : StackBaseShape base(clasp, parent, objectFlags);
1444 3712724 : nbase = BaseShape::getUnowned(cx, base);
1445 3712724 : if (!nbase)
1446 0 : return NULL;
1447 :
1448 3712724 : Shape *shape = JS_PROPERTY_TREE(cx).newShape(cx);
1449 3712724 : if (!shape)
1450 0 : return NULL;
1451 3712724 : new (shape) EmptyShape(nbase, nfixed);
1452 :
1453 3712724 : InitialShapeEntry entry;
1454 3712724 : entry.shape = shape;
1455 3712724 : entry.proto = proto;
1456 :
1457 3712724 : if (!table.relookupOrAdd(p, lookup, entry))
1458 0 : return NULL;
1459 :
1460 3712724 : return shape;
1461 : }
1462 :
1463 : void
1464 35303 : NewObjectCache::invalidateEntriesForShape(JSContext *cx, Shape *shape, JSObject *proto)
1465 : {
1466 35303 : Class *clasp = shape->getObjectClass();
1467 :
1468 35303 : gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
1469 35303 : if (CanBeFinalizedInBackground(kind, clasp))
1470 35303 : kind = GetBackgroundAllocKind(kind);
1471 :
1472 35303 : GlobalObject *global = &shape->getObjectParent()->global();
1473 35303 : types::TypeObject *type = proto->getNewType(cx);
1474 :
1475 : EntryIndex entry;
1476 35303 : if (lookupGlobal(clasp, global, kind, &entry))
1477 20101 : PodZero(&entries[entry]);
1478 35303 : if (!proto->isGlobal() && lookupProto(clasp, proto, kind, &entry))
1479 15202 : PodZero(&entries[entry]);
1480 35303 : if (lookupType(clasp, type, kind, &entry))
1481 0 : PodZero(&entries[entry]);
1482 35303 : }
1483 :
1484 : /* static */ void
1485 35303 : EmptyShape::insertInitialShape(JSContext *cx, Shape *shape, JSObject *proto)
1486 : {
1487 : InitialShapeEntry::Lookup lookup(shape->getObjectClass(), proto, shape->getObjectParent(),
1488 35303 : shape->numFixedSlots(), shape->getObjectFlags());
1489 :
1490 35303 : InitialShapeSet::Ptr p = cx->compartment->initialShapes.lookup(lookup);
1491 35303 : JS_ASSERT(p);
1492 :
1493 35303 : InitialShapeEntry &entry = const_cast<InitialShapeEntry &>(*p);
1494 35303 : JS_ASSERT(entry.shape->isEmptyShape());
1495 :
1496 : /* The new shape had better be rooted at the old one. */
1497 : #ifdef DEBUG
1498 35303 : const Shape *nshape = shape;
1499 183504 : while (!nshape->isEmptyShape())
1500 112898 : nshape = nshape->previous();
1501 35303 : JS_ASSERT(nshape == entry.shape);
1502 : #endif
1503 :
1504 35303 : entry.shape = shape;
1505 :
1506 : /*
1507 : * This affects the shape that will be produced by the various NewObject
1508 : * methods, so clear any cache entry referring to the old shape. This is
1509 : * not required for correctness (though it may bust on the above asserts):
1510 : * the NewObject must always check for a nativeEmpty() result and generate
1511 : * the appropriate properties if found. Clearing the cache entry avoids
1512 : * this duplicate regeneration.
1513 : */
1514 35303 : cx->compartment->newObjectCache.invalidateEntriesForShape(cx, shape, proto);
1515 35303 : }
1516 :
1517 : void
1518 122478 : JSCompartment::sweepInitialShapeTable(JSContext *cx)
1519 : {
1520 122478 : if (initialShapes.initialized()) {
1521 10259386 : for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) {
1522 10187784 : const InitialShapeEntry &entry = e.front();
1523 10187784 : if (!entry.shape->isMarked() || (entry.proto && !entry.proto->isMarked()))
1524 3712279 : e.removeFront();
1525 : }
1526 : }
1527 122478 : }
|