js/src/vm/Shape.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial