js/src/vm/Shape.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial