Back out a81f2aed9c9b due to orange.
authorBrendan Eich <brendan@mozilla.org>
Fri, 18 Jun 2010 17:39:22 -0700
changeset 47475 bacfeaad8fce49dc1f83fea58036fa2c9d604063
parent 47474 27df663a0ab543b00268e78d18a53240cd17a22a
child 47476 7edf86f575bd19661a641730826a457ad60edf76
push idunknown
push userunknown
push dateunknown
milestone1.9.3a5pre
backs outa81f2aed9c9bc6bbf30e81405426505ddcbe9906
Back out a81f2aed9c9b due to orange.
js/src/jsapi.h
js/src/jsbuiltins.cpp
js/src/jscntxt.h
js/src/jsemit.cpp
js/src/jsfun.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jsops.cpp
js/src/jsparse.cpp
js/src/jspropertytree.cpp
js/src/jspubtd.h
js/src/jsscope.cpp
js/src/jsscope.h
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1512,17 +1512,17 @@ struct JSClass {
     /* Optionally non-null members start here. */
     JSGetObjectOps      getObjectOps;
     JSCheckAccessOp     checkAccess;
     JSNative            call;
     JSNative            construct;
     JSXDRObjectOp       xdrObject;
     JSHasInstanceOp     hasInstance;
     JSMarkOp            mark;
-    void                (*reserved0)(void);
+    JSReserveSlotsOp    reserveSlots;
 };
 
 struct JSExtendedClass {
     JSClass             base;
     JSEqualityOp        equality;
     JSObjectOp          outerObject;
     JSObjectOp          innerObject;
     JSIteratorOp        iteratorObject;
--- a/js/src/jsbuiltins.cpp
+++ b/js/src/jsbuiltins.cpp
@@ -211,17 +211,17 @@ AddPropertyHelper(JSContext* cx, JSObjec
         scope = js_GetMutableScope(cx, obj);
         if (!scope)
             goto exit_trace;
     } else {
         JS_ASSERT(!scope->hasProperty(sprop));
     }
 
     if (!scope->table) {
-        if (slot < obj->numSlots()) {
+        if (slot < obj->numSlots() && !obj->getClass()->reserveSlots) {
             JS_ASSERT(JSVAL_IS_VOID(obj->getSlot(scope->freeslot)));
             ++scope->freeslot;
         } else {
             if (!js_AllocSlot(cx, obj, &slot))
                 goto exit_trace;
 
             if (slot != sprop->slot) {
                 js_FreeSlot(cx, obj, slot);
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1438,17 +1438,16 @@ struct JSRuntime {
     volatile uint32     shapeGen;
 
     /* Literal table maintained by jsatom.c functions. */
     JSAtomState         atomState;
 
     JSEmptyScope          *emptyArgumentsScope;
     JSEmptyScope          *emptyBlockScope;
     JSEmptyScope          *emptyCallScope;
-    JSEmptyScope          *emptyWithScope;
 
     /*
      * Various metering fields are defined at the end of JSRuntime. In this
      * way there is no need to recompile all the code that refers to other
      * fields of JSRuntime after enabling the corresponding metering macro.
      */
 #ifdef JS_DUMP_ENUM_CACHE_STATS
     int32               nativeEnumProbes;
--- a/js/src/jsemit.cpp
+++ b/js/src/jsemit.cpp
@@ -60,23 +60,21 @@
 #include "jsemit.h"
 #include "jsfun.h"
 #include "jsnum.h"
 #include "jsopcode.h"
 #include "jsparse.h"
 #include "jsregexp.h"
 #include "jsscan.h"
 #include "jsscope.h"
+#include "jsscopeinlines.h"
 #include "jsscript.h"
 #include "jsautooplen.h"        // generated headers last
 #include "jsstaticcheck.h"
 
-#include "jsobjinlines.h"
-#include "jsscopeinlines.h"
-
 /* Allocation chunk counts, must be powers of two in general. */
 #define BYTECODE_CHUNK  256     /* code allocation increment */
 #define SRCNOTE_CHUNK   64      /* initial srcnote allocation increment */
 #define TRYNOTE_CHUNK   64      /* trynote allocation increment */
 
 /* Macros to compute byte sizes from typed element counts. */
 #define BYTECODE_SIZE(n)        ((n) * sizeof(jsbytecode))
 #define SRCNOTE_SIZE(n)         ((n) * sizeof(jssrcnote))
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -669,16 +669,22 @@ args_or_call_trace(JSTracer *trc, JSObje
         JSObject *obj = js_FloatingFrameToGenerator(fp)->obj;
         JS_CALL_OBJECT_TRACER(trc, obj, "generator object");
     }
 }
 #else
 # define args_or_call_trace NULL
 #endif
 
+static uint32
+args_reserveSlots(JSContext *cx, JSObject *obj)
+{
+    return obj->getArgsLength();
+}
+
 /*
  * The Arguments class is not initialized via JS_InitClass, and must not be,
  * because its name is "Object".  Per ECMA, that causes instances of it to
  * delegate to the object named by Object.prototype.  It also ensures that
  * arguments.toString() returns "[object Object]".
  *
  * The JSClass functions below collaborate to lazily reflect and synchronize
  * actual argument values, argument count, and callee function object stored
@@ -692,17 +698,17 @@ JSClass js_ArgumentsClass = {
     JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_Object),
     JS_PropertyStub,    args_delProperty,
     JS_PropertyStub,    JS_PropertyStub,
     args_enumerate,     (JSResolveOp) args_resolve,
     JS_ConvertStub,     NULL,
     NULL,               NULL,
     NULL,               NULL,
     NULL,               NULL,
-    JS_CLASS_TRACE(args_or_call_trace), NULL
+    JS_CLASS_TRACE(args_or_call_trace), args_reserveSlots
 };
 
 const uint32 JSSLOT_CALLEE =                    JSSLOT_PRIVATE + 1;
 const uint32 JSSLOT_CALL_ARGUMENTS =            JSSLOT_PRIVATE + 2;
 const uint32 CALL_CLASS_FIXED_RESERVED_SLOTS =  2;
 
 /*
  * A Declarative Environment object stores its active JSStackFrame pointer in
@@ -1253,29 +1259,38 @@ call_resolve(JSContext *cx, JSObject *ob
         *objp = obj;
         return JS_TRUE;
     }
 
     /* Control flow reaches here only if id was not resolved. */
     return JS_TRUE;
 }
 
+static uint32
+call_reserveSlots(JSContext *cx, JSObject *obj)
+{
+    JSFunction *fun;
+
+    fun = js_GetCallObjectFunction(obj);
+    return fun->countArgsAndVars();
+}
+
 JS_FRIEND_DATA(JSClass) js_CallClass = {
     "Call",
     JSCLASS_HAS_PRIVATE |
     JSCLASS_HAS_RESERVED_SLOTS(CALL_CLASS_FIXED_RESERVED_SLOTS) |
     JSCLASS_NEW_RESOLVE | JSCLASS_IS_ANONYMOUS | JSCLASS_MARK_IS_TRACE,
     JS_PropertyStub,    JS_PropertyStub,
     JS_PropertyStub,    JS_PropertyStub,
     call_enumerate,     (JSResolveOp)call_resolve,
     NULL,               NULL,
     NULL,               NULL,
     NULL,               NULL,
     NULL,               NULL,
-    JS_CLASS_TRACE(args_or_call_trace), NULL
+    JS_CLASS_TRACE(args_or_call_trace), call_reserveSlots
 };
 
 /* Generic function tinyids. */
 enum {
     FUN_ARGUMENTS   = -1,       /* predefined arguments local variable */
     FUN_LENGTH      = -2,       /* number of actual args, arity if inactive */
     FUN_ARITY       = -3,       /* number of formal parameters; desired argc */
     FUN_NAME        = -4,       /* function name, "" if anonymous */
@@ -1789,33 +1804,47 @@ JSFunction::sharpSlotBase(JSContext *cx)
 uint32
 JSFunction::countInterpretedReservedSlots() const
 {
     JS_ASSERT(FUN_INTERPRETED(this));
 
     return (u.i.nupvars == 0) ? 0 : u.i.script->upvars()->length;
 }
 
+static uint32
+fun_reserveSlots(JSContext *cx, JSObject *obj)
+{
+    /*
+     * We use getPrivate and not GET_FUNCTION_PRIVATE because during
+     * js_InitFunctionClass invocation the function is called before the
+     * private slot of the function object is set.
+     */
+    JSFunction *fun = (JSFunction *) obj->getPrivate();
+    return (fun && FUN_INTERPRETED(fun))
+           ? fun->countInterpretedReservedSlots()
+           : 0;
+}
+
 /*
  * Reserve two slots in all function objects for XPConnect.  Note that this
  * does not bloat every instance, only those on which reserved slots are set,
  * and those on which ad-hoc properties are defined.
  */
 JS_FRIEND_DATA(JSClass) js_FunctionClass = {
     js_Function_str,
     JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_HAS_RESERVED_SLOTS(2) |
     JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_Function),
     JS_PropertyStub,  JS_PropertyStub,
     JS_PropertyStub,  JS_PropertyStub,
     JS_EnumerateStub, (JSResolveOp)fun_resolve,
     JS_ConvertStub,   fun_finalize,
     NULL,             NULL,
     NULL,             NULL,
     js_XDRFunctionObject, fun_hasInstance,
-    JS_CLASS_TRACE(fun_trace), NULL
+    JS_CLASS_TRACE(fun_trace), fun_reserveSlots
 };
 
 static JSBool
 fun_toStringHelper(JSContext *cx, uint32_t indent, uintN argc, jsval *vp)
 {
     jsval fval;
     JSObject *obj;
     JSFunction *fun;
@@ -2439,17 +2468,17 @@ js_AllocFlatClosure(JSContext *cx, JSFun
                ? fun->u.i.script->upvars()->length
                : 0) == fun->u.i.nupvars);
 
     JSObject *closure = CloneFunctionObject(cx, fun, scopeChain);
     if (!closure)
         return closure;
 
     uint32 nslots = fun->countInterpretedReservedSlots();
-    if (nslots == 0)
+    if (!nslots)
         return closure;
     if (!js_EnsureReservedSlots(cx, closure, nslots))
         return NULL;
 
     return closure;
 }
 
 JS_DEFINE_CALLINFO_3(extern, OBJECT, js_AllocFlatClosure,
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2949,23 +2949,21 @@ JSClass js_WithClass = {
     0,0,0,0,0,0,0
 };
 
 JS_REQUIRES_STACK JSObject *
 js_NewWithObject(JSContext *cx, JSObject *proto, JSObject *parent, jsint depth)
 {
     JSObject *obj;
 
-    obj = js_NewGCObject(cx);
+    obj = NewObject(cx, &js_WithClass, proto, parent);
     if (!obj)
         return NULL;
-    obj->init(&js_WithClass, proto, parent,
-              reinterpret_cast<jsval>(js_FloatingFrameIfGenerator(cx, cx->fp)));
+    obj->setPrivate(js_FloatingFrameIfGenerator(cx, cx->fp));
     OBJ_SET_BLOCK_DEPTH(cx, obj, depth);
-    obj->map = cx->runtime->emptyWithScope->hold();
 
     AutoObjectRooter tvr(cx, obj);
     JSObject *thisp = proto->thisObject(cx);
     if (!thisp)
         return NULL;
     obj->setWithThis(thisp);
 
     return obj;
@@ -2996,19 +2994,16 @@ js_CloneBlockObject(JSContext *cx, JSObj
     /* The caller sets parent on its own. */
     jsval priv = (jsval)js_FloatingFrameIfGenerator(cx, fp);
     clone->init(&js_BlockClass, proto, NULL, priv);
     clone->fslots[JSSLOT_BLOCK_DEPTH] = proto->fslots[JSSLOT_BLOCK_DEPTH];
 
     JS_ASSERT(cx->runtime->emptyBlockScope->freeslot == JSSLOT_BLOCK_DEPTH + 1);
     clone->map = cx->runtime->emptyBlockScope->hold();
     JS_ASSERT(OBJ_IS_CLONED_BLOCK(clone));
-
-    if (!js_EnsureReservedSlots(cx, clone, OBJ_BLOCK_COUNT(cx, proto)))
-        return NULL;
     return clone;
 }
 
 JS_REQUIRES_STACK JSBool
 js_PutBlockObject(JSContext *cx, JSBool normalUnwind)
 {
     /* Blocks have one fixed slot available for the first local.*/
     JS_STATIC_ASSERT(JS_INITIAL_NSLOTS == JSSLOT_BLOCK_DEPTH + 2);
@@ -3022,33 +3017,38 @@ js_PutBlockObject(JSContext *cx, JSBool 
     /*
      * Block objects should never be exposed to scripts. Thus the clone should
      * not own the property map and rather always share it with the prototype
      * object. This allows us to skip updating obj->scope()->freeslot after
      * we copy the stack slots into reserved slots.
      */
     JS_ASSERT(obj->scope()->object != obj);
 
-    /* Block objects should have all reserved slots allocated early. */
-    uintN count = OBJ_BLOCK_COUNT(cx, obj);
-    JS_ASSERT(obj->numSlots() == JSSLOT_BLOCK_DEPTH + 1 + count);
+    /* Block objects should not have reserved slots before they are put. */
+    JS_ASSERT(obj->numSlots() == JS_INITIAL_NSLOTS);
 
     /* The block and its locals must be on the current stack for GC safety. */
     uintN depth = OBJ_BLOCK_DEPTH(cx, obj);
+    uintN count = OBJ_BLOCK_COUNT(cx, obj);
     JS_ASSERT(depth <= (size_t) (cx->regs->sp - StackBase(fp)));
     JS_ASSERT(count <= (size_t) (cx->regs->sp - StackBase(fp) - depth));
 
     /* See comments in CheckDestructuring from jsparse.cpp. */
     JS_ASSERT(count >= 1);
 
     depth += fp->script->nfixed;
     obj->fslots[JSSLOT_BLOCK_DEPTH + 1] = fp->slots()[depth];
     if (normalUnwind && count > 1) {
         --count;
-        memcpy(obj->dslots, fp->slots() + depth + 1, count * sizeof(jsval));
+        JS_LOCK_OBJ(cx, obj);
+        if (!obj->allocSlots(cx, JS_INITIAL_NSLOTS + count))
+            normalUnwind = JS_FALSE;
+        else
+            memcpy(obj->dslots, fp->slots() + depth + 1, count * sizeof(jsval));
+        JS_UNLOCK_OBJ(cx, obj);
     }
 
     /* We must clear the private slot even with errors. */
     obj->setPrivate(NULL);
     fp->scopeChain = obj->getParent();
     return normalUnwind;
 }
 
@@ -3283,22 +3283,28 @@ js_XDRBlockObject(JSXDRState *xdr, JSObj
                 return false;
         }
     }
     return true;
 }
 
 #endif
 
+static uint32
+block_reserveSlots(JSContext *cx, JSObject *obj)
+{
+    return OBJ_IS_CLONED_BLOCK(obj) ? OBJ_BLOCK_COUNT(cx, obj) : 0;
+}
+
 JSClass js_BlockClass = {
     "Block",
     JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_IS_ANONYMOUS,
     JS_PropertyStub,  JS_PropertyStub,  JS_PropertyStub,   JS_PropertyStub,
     JS_EnumerateStub, JS_ResolveStub,   JS_ConvertStub,    NULL,
-    JSCLASS_NO_OPTIONAL_MEMBERS
+    NULL, NULL, NULL, NULL, NULL, NULL, NULL, block_reserveSlots
 };
 
 JSObject *
 js_InitObjectClass(JSContext *cx, JSObject *obj)
 {
     JSObject *proto = js_InitClass(cx, obj, NULL, &js_ObjectClass, js_Object, 1,
                                    object_props, object_methods, NULL, object_static_methods);
     if (!proto)
@@ -3900,16 +3906,22 @@ js_ConstructObject(JSContext *cx, JSClas
  * last added won't recycle the deleted props' slots.
  */
 JSBool
 js_AllocSlot(JSContext *cx, JSObject *obj, uint32 *slotp)
 {
     JSScope *scope = obj->scope();
     JS_ASSERT(scope->object == obj);
 
+    JSClass *clasp = obj->getClass();
+    if (scope->freeslot == JSSLOT_FREE(clasp) && clasp->reserveSlots) {
+        /* Adjust scope->freeslot to include computed reserved slots, if any. */
+        scope->freeslot += clasp->reserveSlots(cx, obj);
+    }
+
     if (scope->freeslot >= obj->numSlots() &&
         !obj->growSlots(cx, scope->freeslot + 1)) {
         return JS_FALSE;
     }
 
     /* js_ReallocSlots or js_FreeSlot should set the free slots to void. */
     JS_ASSERT(JSVAL_IS_VOID(obj->getSlot(scope->freeslot)));
     *slotp = scope->freeslot++;
@@ -6140,48 +6152,80 @@ js_Clear(JSContext *cx, JSObject *obj)
         n = JSSLOT_FREE(obj->getClass());
         while (--i >= n)
             obj->setSlot(i, JSVAL_VOID);
         scope->freeslot = n;
     }
     JS_UNLOCK_OBJ(cx, obj);
 }
 
+/* On failure the function unlocks the object. */
+static bool
+ReservedSlotIndexOK(JSContext *cx, JSObject *obj, JSClass *clasp,
+                    uint32 index, uint32 limit)
+{
+    JS_ASSERT(JS_IS_OBJ_LOCKED(cx, obj));
+
+    /* Check the computed, possibly per-instance, upper bound. */
+    if (clasp->reserveSlots)
+        limit += clasp->reserveSlots(cx, obj);
+    if (index >= limit) {
+        JS_UNLOCK_OBJ(cx, obj);
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                             JSMSG_RESERVED_SLOT_RANGE);
+        return false;
+    }
+    return true;
+}
+
 bool
 js_GetReservedSlot(JSContext *cx, JSObject *obj, uint32 index, jsval *vp)
 {
     if (!obj->isNative()) {
         *vp = JSVAL_VOID;
         return true;
     }
 
-    uint32 slot = JSSLOT_START(obj->getClass()) + index;
+    JSClass *clasp = obj->getClass();
+    uint32 limit = JSCLASS_RESERVED_SLOTS(clasp);
+
     JS_LOCK_OBJ(cx, obj);
+    if (index >= limit && !ReservedSlotIndexOK(cx, obj, clasp, index, limit))
+        return false;
+
+    uint32 slot = JSSLOT_START(clasp) + index;
     *vp = (slot < obj->numSlots()) ? obj->getSlot(slot) : JSVAL_VOID;
     JS_UNLOCK_OBJ(cx, obj);
     return true;
 }
 
 bool
 js_SetReservedSlot(JSContext *cx, JSObject *obj, uint32 index, jsval v)
 {
     if (!obj->isNative())
         return true;
 
     JSClass *clasp = obj->getClass();
-    uint32 slot = JSSLOT_START(clasp) + index;
 
     JS_LOCK_OBJ(cx, obj);
+#ifdef DEBUG
+    uint32 limit = JSCLASS_RESERVED_SLOTS(clasp);
+    JS_ASSERT(index < limit || ReservedSlotIndexOK(cx, obj, clasp, index, limit));
+#endif
+
+    uint32 slot = JSSLOT_START(clasp) + index;
     if (slot >= obj->numSlots()) {
         /*
          * At this point, obj may or may not own scope, and we may or may not
          * need to allocate slots. If scope is shared, scope->freeslot may not
          * be accurate for obj (see comment below).
          */
         uint32 nslots = JSSLOT_FREE(clasp);
+        if (clasp->reserveSlots)
+            nslots += clasp->reserveSlots(cx, obj);
         JS_ASSERT(slot < nslots);
         if (!obj->allocSlots(cx, nslots)) {
             JS_UNLOCK_OBJ(cx, obj);
             return false;
         }
     }
 
     /*
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -973,17 +973,19 @@ js_AllocSlot(JSContext *cx, JSObject *ob
 
 extern void
 js_FreeSlot(JSContext *cx, JSObject *obj, uint32 slot);
 
 /*
  * Ensure that the object has at least JSCLASS_RESERVED_SLOTS(clasp)+nreserved
  * slots. The function can be called only for native objects just created with
  * js_NewObject or its forms. In particular, the object should not be shared
- * between threads and its dslots array must be null.
+ * between threads and its dslots array must be null. nreserved must match the
+ * value that JSClass.reserveSlots (if any) would return after the object is
+ * fully initialized.
  */
 bool
 js_EnsureReservedSlots(JSContext *cx, JSObject *obj, size_t nreserved);
 
 extern jsid
 js_CheckForStringIndex(jsid id);
 
 /*
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -589,25 +589,24 @@ InitScopeForObject(JSContext* cx, JSObje
             scope = NULL;
         }
     }
 
     if (!scope) {
         scope = JSScope::create(cx, ops, clasp, obj, js_GenerateShape(cx, false));
         if (!scope)
             goto bad;
-        uint32 freeslot = JSSLOT_FREE(clasp);
-        JS_ASSERT(freeslot >= scope->freeslot);
-        if (freeslot > JS_INITIAL_NSLOTS && !obj->allocSlots(cx, freeslot))
+
+        /* Let JSScope::create set freeslot so as to reserve slots. */
+        JS_ASSERT(scope->freeslot >= JSSLOT_PRIVATE);
+        if (scope->freeslot > JS_INITIAL_NSLOTS &&
+            !obj->allocSlots(cx, scope->freeslot)) {
+            scope->destroy(cx);
             goto bad;
-        scope->freeslot = freeslot;
-#ifdef DEBUG
-        if (freeslot < obj->numSlots())
-            obj->setSlot(freeslot, JSVAL_VOID);
-#endif
+        }
     }
 
     obj->map = scope;
     return true;
 
   bad:
     /* The GC nulls map initially. It should still be null on error. */
     JS_ASSERT(!obj->map);
--- a/js/src/jsops.cpp
+++ b/js/src/jsops.cpp
@@ -1734,49 +1734,52 @@ BEGIN_CASE(JSOP_SETMETHOD)
                  *
                  * We may want to remove hazard conditions above and
                  * inline compensation code here, depending on
                  * real-world workloads.
                  */
                 PCMETER(cache->pchits++);
                 PCMETER(cache->addpchits++);
 
-                if (slot < obj->numSlots()) {
+                /*
+                 * Beware classes such as Function that use the
+                 * reserveSlots hook to allocate a number of reserved
+                 * slots that may vary with obj.
+                 */
+                if (slot < obj->numSlots() &&
+                    !obj->getClass()->reserveSlots) {
                     ++scope->freeslot;
                 } else {
                     if (!js_AllocSlot(cx, obj, &slot))
                         goto error;
-                    JS_ASSERT(slot + 1 == scope->freeslot);
                 }
 
                 /*
                  * If this obj's number of reserved slots differed, or
                  * if something created a hash table for scope, we must
                  * pay the price of JSScope::putProperty.
                  *
-                 * (A built-in object with a pre-allocated but not fixed
-                 * population of reserved slots  hook can cause scopes of the
-                 * same shape to have different freeslot values. Arguments,
-                 * Block, Call, and certain Function objects pre-allocate
-                 * reserveds lots this way. This is what causes the slot !=
-                 * sprop->slot case. See js_GetMutableScope. FIXME 558451)
+                 * (A reserveSlots hook can cause scopes of the same
+                 * shape to have different freeslot values. This is
+                 * what causes the slot != sprop->slot case. See
+                 * js_GetMutableScope.)
                  */
-                if (slot == sprop->slot && !scope->table) {
-                    scope->extend(cx, sprop);
-                } else {
+                if (slot != sprop->slot || scope->table) {
                     JSScopeProperty *sprop2 =
                         scope->putProperty(cx, sprop->id,
                                            sprop->getter(), sprop->setter(),
                                            slot, sprop->attributes(),
                                            sprop->getFlags(), sprop->shortid);
                     if (!sprop2) {
                         js_FreeSlot(cx, obj, slot);
                         goto error;
                     }
                     sprop = sprop2;
+                } else {
+                    scope->extend(cx, sprop);
                 }
 
                 /*
                  * No method change check here because here we are
                  * adding a new property, not updating an existing
                  * slot's value that might contain a method of a
                  * branded scope.
                  */
@@ -3244,16 +3247,17 @@ BEGIN_CASE(JSOP_INITMETHOD)
     /* Load the property's initial value into rval. */
     JS_ASSERT(regs.sp - StackBase(fp) >= 2);
     rval = FETCH_OPND(-1);
 
     /* Load the object being initialized into lval/obj. */
     lval = FETCH_OPND(-2);
     obj = JSVAL_TO_OBJECT(lval);
     JS_ASSERT(obj->isNative());
+    JS_ASSERT(!obj->getClass()->reserveSlots);
 
     JSScope *scope = obj->scope();
     PropertyCacheEntry *entry;
 
     /*
      * Probe the property cache. 
      *
      * We can not assume that the object created by JSOP_NEWINIT is still
--- a/js/src/jsparse.cpp
+++ b/js/src/jsparse.cpp
@@ -3143,63 +3143,65 @@ BindLet(JSContext *cx, BindData *data, J
         if (name) {
             ReportCompileErrorNumber(cx, TS(tc->parser), pn,
                                      JSREPORT_ERROR, JSMSG_REDECLARED_VAR,
                                      (ale && ALE_DEFN(ale)->isConst())
                                      ? js_const_str
                                      : js_variable_str,
                                      name);
         }
-        return false;
+        return JS_FALSE;
     }
 
     n = OBJ_BLOCK_COUNT(cx, blockObj);
     if (n == JS_BIT(16)) {
         ReportCompileErrorNumber(cx, TS(tc->parser), pn,
                                  JSREPORT_ERROR, data->let.overflow);
-        return false;
+        return JS_FALSE;
     }
 
     /*
      * Pass push = true to Define so it pushes an ale ahead of any outer scope.
      * This is balanced by PopStatement, defined immediately below.
      */
     if (!Define(pn, atom, tc, true))
-        return false;
+        return JS_FALSE;
 
     /*
      * Assign block-local index to pn->pn_cookie right away, encoding it as an
      * upvar cookie whose skip tells the current static level. The emitter will
      * adjust the node's slot based on its stack depth model -- and, for global
      * and eval code, Compiler::compileScript will adjust the slot again to
      * include script->nfixed.
      */
     pn->pn_op = JSOP_GETLOCAL;
     pn->pn_cookie = MAKE_UPVAR_COOKIE(tc->staticLevel, n);
     pn->pn_dflags |= PND_LET | PND_BOUND;
 
     /*
-     * Define the let binding's property before storing pn in reserved slot at
-     * reserved slot index (NB: not slot number) n.
+     * Define the let binding's property before storing pn in a reserved slot,
+     * since block_reserveSlots depends on blockObj->scope()->entryCount.
      */
     if (!js_DefineBlockVariable(cx, blockObj, ATOM_TO_JSID(atom), n))
-        return false;
+        return JS_FALSE;
 
     /*
      * Store pn temporarily in what would be reserved slots in a cloned block
      * object (once the prototype's final population is known, after all 'let'
      * bindings for this block have been parsed). We will free these reserved
      * slots in jsemit.cpp:EmitEnterBlock.
      */
     uintN slot = JSSLOT_FREE(&js_BlockClass) + n;
-    if (slot >= blockObj->numSlots() && !blockObj->growSlots(cx, slot + 1))
-        return false;
+    if (slot >= blockObj->numSlots() &&
+        !blockObj->growSlots(cx, slot + 1)) {
+        return JS_FALSE;
+    }
     blockObj->scope()->freeslot = slot + 1;
     blockObj->setSlot(slot, PRIVATE_TO_JSVAL(pn));
-    return true;
+    return JS_TRUE;
 }
 
 static void
 PopStatement(JSTreeContext *tc)
 {
     JSStmtInfo *stmt = tc->topStmt;
 
     if (stmt->flags & SIF_SCOPE) {
--- a/js/src/jspropertytree.cpp
+++ b/js/src/jspropertytree.cpp
@@ -43,17 +43,16 @@
 #include "jsprf.h"
 #include "jsapi.h"
 #include "jscntxt.h"
 #include "jsgc.h"
 #include "jspropertytree.h"
 #include "jsscope.h"
 
 #include "jsnum.h"
-#include "jsobjinlines.h"
 #include "jsscopeinlines.h"
 
 using namespace js;
 
 struct PropertyRootKey
 {
     const JSScopeProperty *firstProp;
     uint32                emptyShape;
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -392,16 +392,31 @@ typedef void
 
 /*
  * DEBUG only callback that JSTraceOp implementation can provide to return
  * a string describing the reference traced with JS_CallTracer.
  */
 typedef void
 (* JSTraceNamePrinter)(JSTracer *trc, char *buf, size_t bufsize);
 
+/*
+ * The optional JSClass.reserveSlots hook allows a class to make computed
+ * per-instance object slots reservations, in addition to or instead of using
+ * JSCLASS_HAS_RESERVED_SLOTS(n) in the JSClass.flags initializer to reserve
+ * a constant-per-class number of slots.  Implementations of this hook should
+ * return the number of slots to reserve, not including any reserved by using
+ * JSCLASS_HAS_RESERVED_SLOTS(n) in JSClass.flags.
+ *
+ * NB: called with obj locked by the JSObjectOps-specific mutual exclusion
+ * mechanism appropriate for obj, so don't nest other operations that might
+ * also lock obj.
+ */
+typedef uint32
+(* JSReserveSlotsOp)(JSContext *cx, JSObject *obj);
+
 /* JSExtendedClass function pointer typedefs. */
 
 typedef JSBool
 (* JSEqualityOp)(JSContext *cx, JSObject *obj, jsval v, JSBool *bp);
 
 /*
  * A generic type for functions mapping an object to another object, or null
  * if an error or exception was thrown on cx.  Used by JSObjectOps.thisObject
--- a/js/src/jsscope.cpp
+++ b/js/src/jsscope.cpp
@@ -58,17 +58,16 @@
 #include "jsfun.h"      /* for JS_ARGS_LENGTH_MAX */
 #include "jslock.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsscope.h"
 #include "jsstr.h"
 #include "jstracer.h"
 
-#include "jsobjinlines.h"
 #include "jsscopeinlines.h"
 
 using namespace js;
 
 uint32
 js_GenerateShape(JSContext *cx, bool gcLocked)
 {
     JSRuntime *rt;
@@ -89,63 +88,54 @@ js_GenerateShape(JSContext *cx, bool gcL
         js_TriggerGC(cx, gcLocked);
     }
     return shape;
 }
 
 JSScope *
 js_GetMutableScope(JSContext *cx, JSObject *obj)
 {
-    JSScope *scope = obj->scope();
+    JSScope *scope, *newscope;
+    JSClass *clasp;
+    uint32 freeslot;
+
+    scope = obj->scope();
     JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, scope));
     if (!scope->isSharedEmpty())
         return scope;
 
     /*
      * Compile-time block objects each have their own scope, created at
      * birth, and runtime clone of a block objects are never mutated.
      */
     JS_ASSERT(obj->getClass() != &js_BlockClass);
-
-    JSScope *newscope = JSScope::create(cx, scope->ops, obj->getClass(), obj, scope->shape);
+    newscope = JSScope::create(cx, scope->ops, obj->getClass(), obj, scope->shape);
     if (!newscope)
         return NULL;
 
     /* The newly allocated scope is single-threaded and, as such, is locked. */
     JS_ASSERT(CX_OWNS_SCOPE_TITLE(cx, newscope));
     JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, newscope));
     obj->map = newscope;
 
-    /*
-     * Subtle dependency on objects that call js_EnsureReservedSlots either:
-     * (a) never escaping anywhere an ad-hoc property could be set on them;
-     * (b) having at least JSSLOT_FREE(obj->getClass()) >= JS_INITIAL_NSLOTS.
-     * Note that (b) depends on fine-tuning of JS_INITIAL_NSLOTS (5).
-     *
-     * Block objects fall into (a); Argument, Call, and Function objects (flat
-     * closures only) fall into (b). All of this goes away soon (FIXME 558451).
-     */
-    JS_ASSERT(newscope->freeslot >= JSSLOT_START(obj->getClass()) &&
-              newscope->freeslot <= JSSLOT_FREE(obj->getClass()));
-    newscope->freeslot = JSSLOT_FREE(obj->getClass());
-
-    uint32 nslots = obj->numSlots();
-    if (newscope->freeslot > nslots && !obj->allocSlots(cx, scope->freeslot)) {
-        newscope->destroy(cx);
-        obj->map = scope;
-        return NULL;
+    JS_ASSERT(newscope->freeslot == JSSLOT_FREE(obj->getClass()));
+    clasp = obj->getClass();
+    if (clasp->reserveSlots) {
+        /*
+         * FIXME: Here we change obj->scope()->freeslot without changing
+         * obj->shape(). If we strengthen the shape guarantees to cover
+         * freeslot, we can eliminate a check in JSOP_SETPROP and in
+         * js_AddProperty. See bug 535416.
+         */
+        freeslot = JSSLOT_FREE(clasp) + clasp->reserveSlots(cx, obj);
+        if (freeslot > obj->numSlots())
+            freeslot = obj->numSlots();
+        if (newscope->freeslot < freeslot)
+            newscope->freeslot = freeslot;
     }
-
-    if (nslots > JS_INITIAL_NSLOTS && nslots > newscope->freeslot)
-        newscope->freeslot = nslots;
-#ifdef DEBUG
-    if (newscope->freeslot < nslots)
-        obj->setSlot(newscope->freeslot, JSVAL_VOID);
-#endif
-
     JS_DROP_ALL_EMPTY_SCOPE_LOCKS(cx, scope);
     static_cast<JSEmptyScope *>(scope)->drop(cx);
     return newscope;
 }
 
 /*
  * JSScope uses multiplicative hashing, _a la_ jsdhash.[ch], but specialized
  * to minimize footprint.  But if a scope has fewer than SCOPE_HASH_THRESHOLD
@@ -222,17 +212,17 @@ JSScope::create(JSContext *cx, const JSO
 {
     JS_ASSERT(ops->isNative());
     JS_ASSERT(obj);
 
     JSScope *scope = cx->create<JSScope>(ops, obj);
     if (!scope)
         return NULL;
 
-    scope->freeslot = JSSLOT_START(clasp);
+    scope->freeslot = JSSLOT_FREE(clasp);
     scope->flags = cx->runtime->gcRegenShapesScopeFlag;
     scope->initMinimal(cx, shape);
 
 #ifdef JS_THREADSAFE
     js_InitTitle(cx, &scope->title);
 #endif
     JS_RUNTIME_METER(cx->runtime, liveScopes);
     JS_RUNTIME_METER(cx->runtime, totalScopes);
@@ -243,17 +233,17 @@ JSEmptyScope::JSEmptyScope(JSContext *cx
                            JSClass *clasp)
     : JSScope(ops, NULL), clasp(clasp)
 {
     /*
      * This scope holds a reference to the new empty scope. Our only caller,
      * getEmptyScope, also promises to incref on behalf of its caller.
      */
     nrefs = 2;
-    freeslot = JSSLOT_START(clasp);
+    freeslot = JSSLOT_FREE(clasp);
     flags = OWN_SHAPE | cx->runtime->gcRegenShapesScopeFlag;
     initMinimal(cx, js_GenerateShape(cx, false));
 
 #ifdef JS_THREADSAFE
     js_InitTitle(cx, &title);
 #endif
     JS_RUNTIME_METER(cx->runtime, liveScopes);
     JS_RUNTIME_METER(cx->runtime, totalScopes);
@@ -323,17 +313,16 @@ JSScope::initRuntimeState(JSContext *cx)
     rt->emptyBlockScope = cx->create<JSEmptyScope>(cx, &js_ObjectOps, &js_BlockClass);
     if (!rt->emptyBlockScope) {
         JSScope::finishRuntimeState(cx);
         return false;
     }
     JS_ASSERT(rt->emptyBlockScope->shape == JSScope::EMPTY_BLOCK_SHAPE);
     JS_ASSERT(rt->emptyBlockScope->nrefs == 2);
     rt->emptyBlockScope->nrefs = 1;
-    rt->emptyBlockScope->freeslot = JSSLOT_FREE(&js_BlockClass);
 
     rt->emptyCallScope = cx->create<JSEmptyScope>(cx, &js_ObjectOps, &js_CallClass);
     if (!rt->emptyCallScope) {
         JSScope::finishRuntimeState(cx);
         return false;
     }
     JS_ASSERT(rt->emptyCallScope->shape == JSScope::EMPTY_CALL_SHAPE);
     JS_ASSERT(rt->emptyCallScope->nrefs == 2);
@@ -343,27 +332,16 @@ JSScope::initRuntimeState(JSContext *cx)
      * Initialize the shared scope for all empty Call objects so gets for args
      * and vars do not force the creation of a mutable scope for the particular
      * call object being accessed.
      *
      * See comment above for rt->emptyArgumentsScope->freeslot initialization.
      */
     rt->emptyCallScope->freeslot = JS_INITIAL_NSLOTS + JSFunction::MAX_ARGS_AND_VARS;
 
-    /* Same drill for With objects. */
-    rt->emptyWithScope = cx->create<JSEmptyScope>(cx, &js_WithObjectOps, &js_WithClass);
-    if (!rt->emptyWithScope) {
-        JSScope::finishRuntimeState(cx);
-        return false;
-    }
-    JS_ASSERT(rt->emptyWithScope->shape == JSScope::EMPTY_WITH_SHAPE);
-    JS_ASSERT(rt->emptyWithScope->nrefs == 2);
-    rt->emptyWithScope->nrefs = 1;
-    rt->emptyWithScope->freeslot = JSSLOT_FREE(&js_WithClass);
-
     return true;
 }
 
 /* static */
 void
 JSScope::finishRuntimeState(JSContext *cx)
 {
     JSRuntime *rt = cx->runtime;
@@ -374,20 +352,16 @@ JSScope::finishRuntimeState(JSContext *c
     if (rt->emptyBlockScope) {
         rt->emptyBlockScope->drop(cx);
         rt->emptyBlockScope = NULL;
     }
     if (rt->emptyCallScope) {
         rt->emptyCallScope->drop(cx);
         rt->emptyCallScope = NULL;
     }
-    if (rt->emptyWithScope) {
-        rt->emptyWithScope->drop(cx);
-        rt->emptyWithScope = NULL;
-    }
 }
 
 JS_STATIC_ASSERT(sizeof(JSHashNumber) == 4);
 JS_STATIC_ASSERT(sizeof(jsid) == JS_BYTES_PER_WORD);
 
 #if JS_BYTES_PER_WORD == 4
 # define HASH_ID(id) ((JSHashNumber)(id))
 #elif JS_BYTES_PER_WORD == 8
--- a/js/src/jsscope.h
+++ b/js/src/jsscope.h
@@ -513,18 +513,17 @@ struct JSScope : public JSObjectMap
 
     static bool initRuntimeState(JSContext *cx);
     static void finishRuntimeState(JSContext *cx);
 
     enum {
         EMPTY_ARGUMENTS_SHAPE = 1,
         EMPTY_BLOCK_SHAPE     = 2,
         EMPTY_CALL_SHAPE      = 3,
-        EMPTY_WITH_SHAPE      = 4,
-        LAST_RESERVED_SHAPE   = 4
+        LAST_RESERVED_SHAPE   = 3
     };
 };
 
 struct JSEmptyScope : public JSScope
 {
     JSClass * const clasp;
     jsrefcount      nrefs;              /* count of all referencing objects */