Bug 888106 - Add too-much-recursion detection to isExtensible tests, and make the isExtensible hook capable of failing. r=bholley, r=ejpbruel
authorJeff Walden <jwalden@mit.edu>
Fri, 28 Jun 2013 14:01:09 -0700
changeset 149450 da9d61adaed29284c020f787d846dab72268793a
parent 149449 3e6afe9129c367d7a5d3a615c71c89fbb0bd68df
child 149451 ecdfb39891a77bfee56934afbc7bd2d91f8bb010
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley, ejpbruel
bugs888106
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 888106 - Add too-much-recursion detection to isExtensible tests, and make the isExtensible hook capable of failing. r=bholley, r=ejpbruel
dom/base/nsGlobalWindow.cpp
dom/bindings/DOMJSProxyHandler.cpp
dom/bindings/DOMJSProxyHandler.h
js/src/builtin/Intl.cpp
js/src/builtin/Object.cpp
js/src/ion/BaselineIC.cpp
js/src/ion/IonCaches.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jsproxy.cpp
js/src/jsproxy.h
js/src/jswrapper.cpp
js/src/jswrapper.h
js/src/vm/Debugger.cpp
js/src/vm/GlobalObject.cpp
js/src/vm/ObjectImpl-inl.h
js/src/vm/ObjectImpl.cpp
js/src/vm/ObjectImpl.h
js/src/vm/ScopeObject.cpp
js/src/vm/Shape.cpp
js/xpconnect/wrappers/XrayWrapper.cpp
js/xpconnect/wrappers/XrayWrapper.h
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -564,17 +564,18 @@ public:
     return false;
   }
 
   virtual const char *className(JSContext *cx,
                                 JS::Handle<JSObject*> wrapper) MOZ_OVERRIDE;
   virtual void finalize(JSFreeOp *fop, JSObject *proxy) MOZ_OVERRIDE;
 
   // Fundamental traps
-  virtual bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
+  virtual bool isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible)
+                           MOZ_OVERRIDE;
   virtual bool preventExtensions(JSContext *cx,
                                  JS::Handle<JSObject*> proxy) MOZ_OVERRIDE;
   virtual bool getPropertyDescriptor(JSContext* cx,
                                      JS::Handle<JSObject*> proxy,
                                      JS::Handle<jsid> id,
                                      JSPropertyDescriptor* desc,
                                      unsigned flags) MOZ_OVERRIDE;
   virtual bool getOwnPropertyDescriptor(JSContext* cx,
@@ -636,21 +637,23 @@ protected:
                                                    JS::Handle<JSObject*> proxy,
                                                    JS::Handle<jsid> id);
 
   bool AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy,
                                   JS::AutoIdVector &props);
 };
 
 bool
-nsOuterWindowProxy::isExtensible(JSObject *proxy)
+nsOuterWindowProxy::isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy,
+                                 bool *extensible)
 {
   // If [[Extensible]] could be false, then navigating a window could navigate
   // to a window that's [[Extensible]] after being at one that wasn't: an
   // invariant violation.  So always report true for this.
+  *extensible = true;
   return true;
 }
 
 bool
 nsOuterWindowProxy::preventExtensions(JSContext *cx,
                                       JS::Handle<JSObject*> proxy)
 {
   // See above.
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -139,19 +139,21 @@ DOMProxyHandler::EnsureExpandoObject(JSC
 
   cache->SetPreservingWrapper(true);
   js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, ObjectValue(*expando));
 
   return expando;
 }
 
 bool
-DOMProxyHandler::isExtensible(JSObject *proxy)
+DOMProxyHandler::isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible)
 {
-  return true; // always extensible per WebIDL
+  // always extensible per WebIDL
+  *extensible = true;
+  return true;
 }
 
 bool
 DOMProxyHandler::preventExtensions(JSContext *cx, JS::Handle<JSObject*> proxy)
 {
   // Throw a TypeError, per WebIDL.
   JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CHANGE_EXTENSIBILITY);
   return false;
--- a/dom/bindings/DOMJSProxyHandler.h
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -46,17 +46,17 @@ public:
     return defineProperty(cx, proxy, id, desc, &unused);
   }
   virtual bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
                               JSPropertyDescriptor* desc, bool* defined);
   bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
                JS::Handle<jsid> id, bool* bp) MOZ_OVERRIDE;
   bool enumerate(JSContext* cx, JS::Handle<JSObject*> proxy, JS::AutoIdVector& props) MOZ_OVERRIDE;
   bool has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, bool* bp) MOZ_OVERRIDE;
-  bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
+  bool isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible) MOZ_OVERRIDE;
 
   static JSObject* GetExpandoObject(JSObject* obj)
   {
     MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
     JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
     if (v.isObject()) {
       return &v.toObject();
     }
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -598,17 +598,20 @@ Collator(JSContext *cx, CallArgs args, b
         RootedValue self(cx, args.thisv());
         if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
             // 10.1.2.1 step 4
             obj = ToObject(cx, self);
             if (!obj)
                 return false;
 
             // 10.1.2.1 step 5
-            if (!obj->isExtensible())
+            bool extensible;
+            if (!JSObject::isExtensible(cx, obj, &extensible))
+                return false;
+            if (!extensible)
                 return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
         } else {
             // 10.1.2.1 step 3.a
             construct = true;
         }
     }
     if (construct) {
         // 10.1.3.1 paragraph 2
@@ -1076,17 +1079,20 @@ NumberFormat(JSContext *cx, CallArgs arg
         RootedValue self(cx, args.thisv());
         if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
             // 11.1.2.1 step 4
             obj = ToObject(cx, self);
             if (!obj)
                 return false;
 
             // 11.1.2.1 step 5
-            if (!obj->isExtensible())
+            bool extensible;
+            if (!JSObject::isExtensible(cx, obj, &extensible))
+                return false;
+            if (!extensible)
                 return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
         } else {
             // 11.1.2.1 step 3.a
             construct = true;
         }
     }
     if (construct) {
         // 11.1.3.1 paragraph 2
@@ -1526,17 +1532,20 @@ DateTimeFormat(JSContext *cx, CallArgs a
         RootedValue self(cx, args.thisv());
         if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
             // 12.1.2.1 step 4
             obj = ToObject(cx, self);
             if (!obj)
                 return false;
 
             // 12.1.2.1 step 5
-            if (!obj->isExtensible())
+            bool extensible;
+            if (!JSObject::isExtensible(cx, obj, &extensible))
+                return false;
+            if (!extensible)
                 return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
         } else {
             // 12.1.2.1 step 3.a
             construct = true;
         }
     }
     if (construct) {
         // 12.1.3.1 paragraph 2
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -880,30 +880,37 @@ obj_defineProperties(JSContext *cx, unsi
 static JSBool
 obj_isExtensible(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject obj(cx);
     if (!GetFirstArgumentAsObject(cx, args, "Object.isExtensible", &obj))
         return false;
 
-    args.rval().setBoolean(obj->isExtensible());
+    bool extensible;
+    if (!JSObject::isExtensible(cx, obj, &extensible))
+        return false;
+    args.rval().setBoolean(extensible);
     return true;
 }
 
 static JSBool
 obj_preventExtensions(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject obj(cx);
     if (!GetFirstArgumentAsObject(cx, args, "Object.preventExtensions", &obj))
         return false;
 
     args.rval().setObject(*obj);
-    if (!obj->isExtensible())
+
+    bool extensible;
+    if (!JSObject::isExtensible(cx, obj, &extensible))
+        return false;
+    if (!extensible)
         return true;
 
     return JSObject::preventExtensions(cx, obj);
 }
 
 static JSBool
 obj_freeze(JSContext *cx, unsigned argc, Value *vp)
 {
--- a/js/src/ion/BaselineIC.cpp
+++ b/js/src/ion/BaselineIC.cpp
@@ -3331,17 +3331,17 @@ IsCacheableSetPropAddSlot(JSContext *cx,
     if (!shape)
         return false;
 
     // Property must be set directly on object, and be last added property of object.
     if (obj != holder || shape != obj->lastProperty())
         return false;
 
     // Object must be extensible, oldShape must be immediate parent of curShape.
-    if (!obj->isExtensible() || obj->lastProperty()->previous() != oldShape)
+    if (!obj->nonProxyIsExtensible() || obj->lastProperty()->previous() != oldShape)
         return false;
 
     // Basic shape checks.
     if (shape->inDictionary() || !shape->hasSlot() || !shape->hasDefaultSetter() ||
         !shape->writable())
     {
         return false;
     }
--- a/js/src/ion/IonCaches.cpp
+++ b/js/src/ion/IonCaches.cpp
@@ -2060,17 +2060,17 @@ IsPropertyAddInlineable(JSContext *cx, H
     if (obj->getClass()->resolve != JS_ResolveStub)
         return false;
 
     // Likewise for a non-default addProperty hook, since we'll need
     // to invoke it.
     if (obj->getClass()->addProperty != JS_PropertyStub)
         return false;
 
-    if (!obj->isExtensible() || !shape->writable())
+    if (!obj->nonProxyIsExtensible() || !shape->writable())
         return false;
 
     // walk up the object prototype chain and ensure that all prototypes
     // are native, and that all prototypes have no getter or setter
     // defined on the property
     for (JSObject *proto = obj->getProto(); proto; proto = proto->getProto()) {
         // if prototype is non-native, don't optimize
         if (!proto->isNative())
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3358,19 +3358,23 @@ JS_NewObjectForConstructor(JSContext *cx
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, *vp);
 
     RootedObject obj(cx, JSVAL_TO_OBJECT(*vp));
     return CreateThis(cx, Valueify(clasp), obj);
 }
 
 JS_PUBLIC_API(JSBool)
-JS_IsExtensible(JSObject *obj)
-{
-    return obj->isExtensible();
+JS_IsExtensible(JSContext *cx, HandleObject obj, JSBool *extensible)
+{
+    bool isExtensible;
+    if (!JSObject::isExtensible(cx, obj, &isExtensible))
+        return false;
+    *extensible = isExtensible;
+    return true;
 }
 
 JS_PUBLIC_API(JSBool)
 JS_IsNative(JSObject *obj)
 {
     return obj->isNative();
 }
 
@@ -3395,17 +3399,20 @@ JS_PUBLIC_API(JSBool)
 JS_DeepFreezeObject(JSContext *cx, JSObject *objArg)
 {
     RootedObject obj(cx, objArg);
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj);
 
     /* Assume that non-extensible objects are already deep-frozen, to avoid divergence. */
-    if (!obj->isExtensible())
+    bool extensible;
+    if (!JSObject::isExtensible(cx, obj, &extensible))
+        return false;
+    if (!extensible)
         return true;
 
     if (!JSObject::freeze(cx, obj))
         return false;
 
     /* Walk slots in obj and if any value is a non-null object, seal it. */
     for (uint32_t i = 0, n = obj->slotSpan(); i < n; ++i) {
         const Value &v = obj->getSlot(i);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3180,17 +3180,17 @@ extern JS_PUBLIC_API(JSObject *)
 JS_NewGlobalObject(JSContext *cx, JSClass *clasp, JSPrincipals *principals,
                    const JS::CompartmentOptions &options = JS::CompartmentOptions());
 
 extern JS_PUBLIC_API(JSObject *)
 JS_NewObject(JSContext *cx, JSClass *clasp, JSObject *proto, JSObject *parent);
 
 /* Queries the [[Extensible]] property of the object. */
 extern JS_PUBLIC_API(JSBool)
-JS_IsExtensible(JSObject *obj);
+JS_IsExtensible(JSContext *cx, JS::HandleObject obj, JSBool *extensible);
 
 extern JS_PUBLIC_API(JSBool)
 JS_IsNative(JSObject *obj);
 
 extern JS_PUBLIC_API(JSRuntime *)
 JS_GetObjectRuntime(JSObject *obj);
 
 /*
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -561,17 +561,17 @@ js::CheckDefineProperty(JSContext *cx, H
         return true;
 
     // ES5 8.12.9 Step 1. Even though we know obj is native, we use generic
     // APIs for shorter, more readable code.
     AutoPropertyDescriptorRooter desc(cx);
     if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
         return false;
 
-    // This does not have to check obj->isExtensible() when !desc.obj (steps
+    // This does not have to check obj's extensibility when !desc.obj (steps
     // 2-3) because the low-level methods JSObject::{add,put}Property check
     // for that.
     if (desc.obj && (desc.attrs & JSPROP_PERMANENT)) {
         // Steps 6-11, skipping step 10.a.ii. Prohibit redefining a permanent
         // property with different metadata, except to make a writable property
         // non-writable.
         if (getter != desc.getter ||
             setter != desc.setter ||
@@ -603,17 +603,20 @@ DefinePropertyOnObject(JSContext *cx, Ha
     JS_ASSERT(!obj->getOps()->lookupGeneric);
     if (!HasOwnProperty<CanGC>(cx, NULL, obj, id, &obj2, &shape))
         return JS_FALSE;
 
     JS_ASSERT(!obj->getOps()->defineProperty);
 
     /* 8.12.9 steps 2-4. */
     if (!shape) {
-        if (!obj->isExtensible())
+        bool extensible;
+        if (!JSObject::isExtensible(cx, obj, &extensible))
+            return false;
+        if (!extensible)
             return Reject(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval);
 
         *rval = true;
 
         if (desc.isGenericDescriptor() || desc.isDataDescriptor()) {
             JS_ASSERT(!obj->getOps()->defineProperty);
             RootedValue v(cx, desc.hasValue() ? desc.value() : UndefinedValue());
             return baseops::DefineGeneric(cx, obj, id, v,
@@ -1126,17 +1129,20 @@ JSObject::getSealedOrFrozenAttributes(un
 }
 
 /* static */ bool
 JSObject::sealOrFreeze(JSContext *cx, HandleObject obj, ImmutabilityType it)
 {
     assertSameCompartment(cx, obj);
     JS_ASSERT(it == SEAL || it == FREEZE);
 
-    if (obj->isExtensible() && !JSObject::preventExtensions(cx, obj))
+    bool extensible;
+    if (!JSObject::isExtensible(cx, obj, &extensible))
+        return false;
+    if (extensible && !JSObject::preventExtensions(cx, obj))
         return false;
 
     AutoIdVector props(cx);
     if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props))
         return false;
 
     /* preventExtensions must sparsify dense objects, so we can assign to holes without checks. */
     JS_ASSERT_IF(obj->isNative(), obj->getDenseCapacity() == 0);
@@ -1215,17 +1221,20 @@ JSObject::sealOrFreeze(JSContext *cx, Ha
         obj->getElementsHeader()->setNonwritableArrayLength();
 
     return true;
 }
 
 /* static */ bool
 JSObject::isSealedOrFrozen(JSContext *cx, HandleObject obj, ImmutabilityType it, bool *resultp)
 {
-    if (obj->isExtensible()) {
+    bool extensible;
+    if (!JSObject::isExtensible(cx, obj, &extensible))
+        return false;
+    if (extensible) {
         *resultp = false;
         return true;
     }
 
     AutoIdVector props(cx);
     if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props))
         return false;
 
@@ -2725,17 +2734,17 @@ JSObject::maybeDensifySparseElements(JSC
      * Only measure the number of indexed properties every log(n) times when
      * populating the object.
      */
     uint32_t slotSpan = obj->slotSpan();
     if (slotSpan != RoundUpPow2(slotSpan))
         return ED_SPARSE;
 
     /* Watch for conditions under which an object's elements cannot be dense. */
-    if (!obj->isExtensible() || obj->watched())
+    if (!obj->nonProxyIsExtensible() || obj->watched())
         return ED_SPARSE;
 
     /*
      * The indexes in the object need to be sufficiently dense before they can
      * be converted to dense mode.
      */
     uint32_t numDenseElements = 0;
     uint32_t newInitializedLength = 0;
@@ -2848,17 +2857,17 @@ ReallocateElements(ThreadSafeContext *tc
 
     return static_cast<js::ObjectElements *>(tcx->realloc_(oldHeader, oldCount * sizeof(HeapSlot),
                                                            newCount * sizeof(HeapSlot)));
 }
 
 bool
 JSObject::growElements(ThreadSafeContext *tcx, uint32_t newcap)
 {
-    JS_ASSERT(isExtensible());
+    JS_ASSERT(nonProxyIsExtensible());
 
     /*
      * When an object with CAPACITY_DOUBLING_MAX or fewer elements needs to
      * grow, double its capacity, to add N elements in amortized O(N) time.
      *
      * Above this limit, grow by 12.5% each time. Speed is still amortized
      * O(N), with a higher constant factor, and we waste less space.
      */
@@ -4461,17 +4470,20 @@ baseops::SetPropertyHelper(JSContext *cx
     }
 
     if (obj->is<ArrayObject>() && id == NameToId(cx->names().length)) {
         Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
         return ArraySetLength(cx, arr, id, attrs, vp, strict);
     }
 
     if (!shape) {
-        if (!obj->isExtensible()) {
+        bool extensible;
+        if (!JSObject::isExtensible(cx, obj, &extensible))
+            return false;
+        if (!extensible) {
             /* Error in strict mode code, warn with extra warnings option, otherwise do nothing. */
             if (strict)
                 return obj->reportNotExtensible(cx);
             if (cx->hasExtraWarningsOption())
                 return obj->reportNotExtensible(cx, JSREPORT_STRICT | JSREPORT_WARNING);
             return true;
         }
 
@@ -5187,17 +5199,17 @@ JSObject::dump()
 {
     JSObject *obj = this;
     fprintf(stderr, "object %p\n", (void *) obj);
     Class *clasp = obj->getClass();
     fprintf(stderr, "class %p %s\n", (void *)clasp, clasp->name);
 
     fprintf(stderr, "flags:");
     if (obj->isDelegate()) fprintf(stderr, " delegate");
-    if (!obj->isExtensible()) fprintf(stderr, " not_extensible");
+    if (!obj->isProxy() && !obj->nonProxyIsExtensible()) fprintf(stderr, " not_extensible");
     if (obj->isIndexed()) fprintf(stderr, " indexed");
 
     if (obj->isNative()) {
         if (obj->inDictionaryMode())
             fprintf(stderr, " inDictionaryMode");
         if (obj->hasShapeTable())
             fprintf(stderr, " hasShapeTable");
     }
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -682,17 +682,17 @@ class JSObject : public js::ObjectImpl
                                          js::HandleShape parent, js::StackShape &child);
 
   protected:
     /*
      * Internal helper that adds a shape not yet mapped by this object.
      *
      * Notes:
      * 1. getter and setter must be normalized based on flags (see jsscope.cpp).
-     * 2. !isExtensible() checking must be done by callers.
+     * 2. Checks for non-extensibility must be done by callers.
      */
     static js::Shape *addPropertyInternal(JSContext *cx,
                                             JS::HandleObject obj, JS::HandleId id,
                                             JSPropertyOp getter, JSStrictPropertyOp setter,
                                             uint32_t slot, unsigned attrs,
                                             unsigned flags, int shortid, js::Shape **spp,
                                             bool allowDictionary);
 
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -379,17 +379,17 @@ JSObject::EnsureDenseResult
 JSObject::extendDenseElements(js::ThreadSafeContext *tcx,
                               uint32_t requiredCapacity, uint32_t extra)
 {
     /*
      * Don't grow elements for non-extensible objects or watched objects. Dense
      * elements can be added/written with no extensible or watchpoint checks as
      * long as there is capacity for them.
      */
-    if (!isExtensible() || watched()) {
+    if (!nonProxyIsExtensible() || watched()) {
         JS_ASSERT(getDenseCapacity() == 0);
         return ED_SPARSE;
     }
 
     /*
      * Don't grow elements for objects which already have sparse indexes.
      * This avoids needing to count non-hole elements in willBeSparseElements
      * every time a new index is added.
--- a/js/src/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -620,19 +620,20 @@ DirectProxyHandler::iterate(JSContext *c
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID);
     JS_ASSERT(!hasPrototype()); // Should never be called if there's a prototype.
     RootedObject target(cx, GetProxyTargetObject(proxy));
     return GetIterator(cx, target, flags, vp);
 }
 
 bool
-DirectProxyHandler::isExtensible(JSObject *proxy)
+DirectProxyHandler::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible)
 {
-    return GetProxyTargetObject(proxy)->isExtensible();
+    RootedObject target(cx, GetProxyTargetObject(proxy));
+    return JSObject::isExtensible(cx, target, extensible);
 }
 
 bool
 DirectProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy)
 {
     RootedObject target(cx, GetProxyTargetObject(proxy));
     return JSObject::preventExtensions(cx, target);
 }
@@ -782,17 +783,17 @@ class ScriptedIndirectProxyHandler : pub
                      MutableHandleValue vp) MOZ_OVERRIDE;
     virtual bool set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
                      bool strict, MutableHandleValue vp) MOZ_OVERRIDE;
     virtual bool keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE;
     virtual bool iterate(JSContext *cx, HandleObject proxy, unsigned flags,
                          MutableHandleValue vp) MOZ_OVERRIDE;
 
     /* Spidermonkey extensions. */
-    virtual bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
+    virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) MOZ_OVERRIDE;
     virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) MOZ_OVERRIDE;
     virtual JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent) MOZ_OVERRIDE;
     virtual bool defaultValue(JSContext *cx, HandleObject obj, JSType hint,
                               MutableHandleValue vp) MOZ_OVERRIDE;
 
@@ -806,19 +807,20 @@ ScriptedIndirectProxyHandler::ScriptedIn
 {
 }
 
 ScriptedIndirectProxyHandler::~ScriptedIndirectProxyHandler()
 {
 }
 
 bool
-ScriptedIndirectProxyHandler::isExtensible(JSObject *proxy)
+ScriptedIndirectProxyHandler::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible)
 {
     // Scripted indirect proxies don't support extensibility changes.
+    *extensible = true;
     return true;
 }
 
 bool
 ScriptedIndirectProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy)
 {
     // See above.
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CHANGE_EXTENSIBILITY);
@@ -1389,17 +1391,20 @@ TrapGetOwnProperty(JSContext *cx, Handle
         bool sealed;
         if (!IsSealed(cx, target, id, &sealed))
             return false;
         if (sealed) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_NC_AS_NE);
             return false;
         }
 
-        if (!target->isExtensible()) {
+        bool extensible;
+        if (!JSObject::isExtensible(cx, target, &extensible))
+            return false;
+        if (!extensible) {
             bool found;
             if (!HasOwn(cx, target, id, &found))
                 return false;
             if (found) {
                 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_E_AS_NE);
                 return false;
             }
         }
@@ -1409,17 +1414,20 @@ TrapGetOwnProperty(JSContext *cx, Handle
     }
 
     // step 8
     bool isFixed;
     if (!HasOwn(cx, target, id, &isFixed))
         return false;
 
     // step 9
-    if (target->isExtensible() && !isFixed) {
+    bool extensible;
+    if (!JSObject::isExtensible(cx, target, &extensible))
+        return false;
+    if (extensible && !isFixed) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_NEW);
         return false;
     }
 
     AutoPropDescArrayRooter descs(cx);
     PropDesc *desc = descs.append();
     if (!desc || !desc->initialize(cx, trapResult))
         return false;
@@ -1490,17 +1498,20 @@ TrapDefineOwnProperty(JSContext *cx, Han
         return false;
 
     // steps 7-8
     if (ToBoolean(trapResult)) {
         bool isFixed;
         if (!HasOwn(cx, target, id, &isFixed))
             return false;
 
-        if (!target->isExtensible() && !isFixed) {
+        bool extensible;
+        if (!JSObject::isExtensible(cx, target, &extensible))
+            return false;
+        if (!extensible && !isFixed) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_DEFINE_NEW);
             return false;
         }
 
         AutoPropDescArrayRooter descs(cx);
         PropDesc *desc = descs.append();
         if (!desc || !desc->initialize(cx, normalizedDesc))
             return false;
@@ -1576,17 +1587,20 @@ ArrayToIdVector(JSContext *cx, HandleObj
         }
 
         // step iv
         bool isFixed;
         if (!HasOwn(cx, target, id, &isFixed))
             return false;
 
         // step v
-        if (!target->isExtensible() && !isFixed) {
+        bool extensible;
+        if (!JSObject::isExtensible(cx, target, &extensible))
+            return false;
+        if (!extensible && !isFixed) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_NEW);
             return false;
         }
 
         // step vi
         if (!props.append(id))
             return false;
     }
@@ -1620,17 +1634,20 @@ ArrayToIdVector(JSContext *cx, HandleObj
         }
 
         // step ii
         bool isFixed;
         if (!HasOwn(cx, target, id, &isFixed))
             return false;
 
         // step iii
-        if (!target->isExtensible() && isFixed) {
+        bool extensible;
+        if (!JSObject::isExtensible(cx, target, &extensible))
+            return false;
+        if (!extensible && isFixed) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_E_AS_NE);
             return false;
         }
     }
 
     // step n
     return true;
 }
@@ -1669,17 +1686,20 @@ ScriptedDirectProxyHandler::preventExten
     RootedValue trapResult(cx);
     if (!Invoke(cx, ObjectValue(*handler), trap, ArrayLength(argv), argv, &trapResult))
         return false;
 
     // step f
     bool success = ToBoolean(trapResult);
     if (success) {
         // step g
-        if (target->isExtensible()) {
+        bool extensible;
+        if (!JSObject::isExtensible(cx, target, &extensible))
+            return false;
+        if (extensible) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_AS_NON_EXTENSIBLE);
             return false;
         }
         return true;
     }
 
     // step h
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CHANGE_EXTENSIBILITY);
@@ -1914,17 +1934,20 @@ ScriptedDirectProxyHandler::has(JSContex
         bool sealed;
         if (!IsSealed(cx, target, id, &sealed))
             return false;
         if (sealed) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_NC_AS_NE);
             return false;
         }
 
-        if (!target->isExtensible()) {
+        bool extensible;
+        if (!JSObject::isExtensible(cx, target, &extensible))
+            return false;
+        if (!extensible) {
             bool isFixed;
             if (!HasOwn(cx, target, id, &isFixed))
                 return false;
             if (isFixed) {
                 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_E_AS_NE);
                 return false;
             }
         }
@@ -1974,32 +1997,40 @@ ScriptedDirectProxyHandler::hasOwn(JSCon
         bool sealed;
         if (!IsSealed(cx, target, id, &sealed))
             return false;
         if (sealed) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_NC_AS_NE);
             return false;
         }
 
-        if (!target->isExtensible()) {
+        bool extensible;
+        if (!JSObject::isExtensible(cx, target, &extensible))
+            return false;
+        if (!extensible) {
             bool isFixed;
             if (!HasOwn(cx, target, id, &isFixed))
                 return false;
             if (isFixed) {
                 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_E_AS_NE);
                 return false;
             }
         }
-    } else if (!target->isExtensible()) {
-        bool isFixed;
-        if (!HasOwn(cx, target, id, &isFixed))
+    } else {
+        bool extensible;
+        if (!JSObject::isExtensible(cx, target, &extensible))
             return false;
-        if (!isFixed) {
-            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_NEW);
-            return false;
+        if (!extensible) {
+            bool isFixed;
+            if (!HasOwn(cx, target, id, &isFixed))
+                return false;
+            if (!isFixed) {
+                JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_NEW);
+                return false;
+            }
         }
     }
 
     // step 9
     *bp = !!success;
     return true;
 }
 
@@ -2573,19 +2604,20 @@ Proxy::iterate(JSContext *cx, HandleObje
         ? !Proxy::keys(cx, proxy, props)
         : !Proxy::enumerate(cx, proxy, props)) {
         return false;
     }
     return EnumeratedIdVectorToIterator(cx, proxy, flags, props, vp);
 }
 
 bool
-Proxy::isExtensible(JSObject *proxy)
+Proxy::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible)
 {
-    return GetProxyHandler(proxy)->isExtensible(proxy);
+    JS_CHECK_RECURSION(cx, return false);
+    return GetProxyHandler(proxy)->isExtensible(cx, proxy, extensible);
 }
 
 bool
 Proxy::preventExtensions(JSContext *cx, HandleObject proxy)
 {
     JS_CHECK_RECURSION(cx, return false);
     BaseProxyHandler *handler = GetProxyHandler(proxy);
     return handler->preventExtensions(cx, proxy);
--- a/js/src/jsproxy.h
+++ b/js/src/jsproxy.h
@@ -124,17 +124,17 @@ class JS_FRIEND_API(BaseProxyHandler)
                      HandleId id, MutableHandleValue vp);
     virtual bool set(JSContext *cx, HandleObject proxy, HandleObject receiver,
                      HandleId id, bool strict, MutableHandleValue vp);
     virtual bool keys(JSContext *cx, HandleObject proxy, AutoIdVector &props);
     virtual bool iterate(JSContext *cx, HandleObject proxy, unsigned flags,
                          MutableHandleValue vp);
 
     /* Spidermonkey extensions. */
-    virtual bool isExtensible(JSObject *proxy) = 0;
+    virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) = 0;
     virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args);
     virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args);
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args);
     virtual bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp);
     virtual bool objectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx);
     virtual const char *className(JSContext *cx, HandleObject proxy);
     virtual JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent);
     virtual bool regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g);
@@ -185,17 +185,17 @@ class JS_PUBLIC_API(DirectProxyHandler) 
     virtual bool set(JSContext *cx, HandleObject proxy, HandleObject receiver,
                      HandleId id, bool strict, MutableHandleValue vp) MOZ_OVERRIDE;
     virtual bool keys(JSContext *cx, HandleObject proxy,
                       AutoIdVector &props) MOZ_OVERRIDE;
     virtual bool iterate(JSContext *cx, HandleObject proxy, unsigned flags,
                          MutableHandleValue vp) MOZ_OVERRIDE;
 
     /* Spidermonkey extensions. */
-    virtual bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
+    virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) MOZ_OVERRIDE;
     virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) MOZ_OVERRIDE;
     virtual bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v,
                              bool *bp) MOZ_OVERRIDE;
     virtual bool objectClassIs(HandleObject obj, ESClassValue classValue,
                                JSContext *cx) MOZ_OVERRIDE;
@@ -237,17 +237,17 @@ class Proxy
     static bool getElementIfPresent(JSContext *cx, HandleObject proxy, HandleObject receiver,
                                     uint32_t index, MutableHandleValue vp, bool *present);
     static bool set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
                     bool strict, MutableHandleValue vp);
     static bool keys(JSContext *cx, HandleObject proxy, AutoIdVector &props);
     static bool iterate(JSContext *cx, HandleObject proxy, unsigned flags, MutableHandleValue vp);
 
     /* Spidermonkey extensions. */
-    static bool isExtensible(JSObject *proxy);
+    static bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible);
     static bool call(JSContext *cx, HandleObject proxy, const CallArgs &args);
     static bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args);
     static bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args);
     static bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp);
     static bool objectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx);
     static const char *className(JSContext *cx, HandleObject proxy);
     static JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent);
     static bool regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g);
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -186,28 +186,22 @@ bool CrossCompartmentWrapper::finalizeIn
             ok = (pre) && (op);                                 \
         }                                                       \
         return ok && (post);                                    \
     JS_END_MACRO
 
 #define NOTHING (true)
 
 bool
-CrossCompartmentWrapper::isExtensible(JSObject *wrapper)
+CrossCompartmentWrapper::isExtensible(JSContext *cx, HandleObject wrapper, bool *extensible)
 {
-    // The lack of a context to enter a compartment here is troublesome.  We
-    // don't know anything about the wrapped object (it might even be a
-    // proxy!), and embeddings' proxy handlers could theoretically trigger
-    // compartment mismatches here (because isExtensible wouldn't be called in
-    // the wrapped object's compartment.  But that's probably not very likely.
-    // (Famous last words.)
-    //
-    // Given that we're likely going to make this method take a context and
-    // maybe be fallible at some point, punt on the issue for now.
-    return wrappedObject(wrapper)->isExtensible();
+    PIERCE(cx, wrapper,
+           NOTHING,
+           Wrapper::isExtensible(cx, wrapper, extensible),
+           NOTHING);
 }
 
 bool
 CrossCompartmentWrapper::preventExtensions(JSContext *cx, HandleObject wrapper)
 {
     PIERCE(cx, wrapper,
            NOTHING,
            Wrapper::preventExtensions(cx, wrapper),
@@ -600,21 +594,22 @@ SecurityWrapper<Base>::SecurityWrapper(u
   : Base(flags)
 {
     Base::setSafeToUnwrap(false);
     BaseProxyHandler::setHasPolicy(true);
 }
 
 template <class Base>
 bool
-SecurityWrapper<Base>::isExtensible(JSObject *wrapper)
+SecurityWrapper<Base>::isExtensible(JSContext *cx, HandleObject wrapper, bool *extensible)
 {
     // Just like BaseProxyHandler, SecurityWrappers claim by default to always
     // be extensible, so as not to leak information about the state of the
     // underlying wrapped thing.
+    *extensible = true;
     return true;
 }
 
 template <class Base>
 bool
 SecurityWrapper<Base>::preventExtensions(JSContext *cx, HandleObject wrapper)
 {
     // See above.
@@ -686,20 +681,21 @@ template class js::SecurityWrapper<Wrapp
 template class js::SecurityWrapper<CrossCompartmentWrapper>;
 
 DeadObjectProxy::DeadObjectProxy()
   : BaseProxyHandler(&sDeadObjectFamily)
 {
 }
 
 bool
-DeadObjectProxy::isExtensible(JSObject *proxy)
+DeadObjectProxy::isExtensible(JSContext *cx, HandleObject proxy, bool *extensible)
 {
     // This is kind of meaningless, but dead-object semantics aside,
     // [[Extensible]] always being true is consistent with other proxy types.
+    *extensible = true;
     return true;
 }
 
 bool
 DeadObjectProxy::preventExtensions(JSContext *cx, HandleObject proxy)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEAD_OBJECT);
     return false;
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -100,17 +100,17 @@ class JS_FRIEND_API(CrossCompartmentWrap
                      HandleId id, MutableHandleValue vp) MOZ_OVERRIDE;
     virtual bool set(JSContext *cx, HandleObject wrapper, HandleObject receiver,
                      HandleId id, bool strict, MutableHandleValue vp) MOZ_OVERRIDE;
     virtual bool keys(JSContext *cx, HandleObject wrapper, AutoIdVector &props) MOZ_OVERRIDE;
     virtual bool iterate(JSContext *cx, HandleObject wrapper, unsigned flags,
                          MutableHandleValue vp) MOZ_OVERRIDE;
 
     /* Spidermonkey extensions. */
-    virtual bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
+    virtual bool isExtensible(JSContext *cx, HandleObject wrapper, bool *extensible) MOZ_OVERRIDE;
     virtual bool call(JSContext *cx, HandleObject wrapper, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool construct(JSContext *cx, HandleObject wrapper, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) MOZ_OVERRIDE;
     virtual bool hasInstance(JSContext *cx, HandleObject wrapper, MutableHandleValue v,
                              bool *bp) MOZ_OVERRIDE;
     virtual const char *className(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
     virtual JSString *fun_toString(JSContext *cx, HandleObject wrapper,
@@ -134,17 +134,17 @@ class JS_FRIEND_API(CrossCompartmentWrap
  * access, relying on derived SecurityWrapper to block access when necessary.
  */
 template <class Base>
 class JS_FRIEND_API(SecurityWrapper) : public Base
 {
   public:
     SecurityWrapper(unsigned flags);
 
-    virtual bool isExtensible(JSObject *wrapper) MOZ_OVERRIDE;
+    virtual bool isExtensible(JSContext *cx, HandleObject wrapper, bool *extensible) MOZ_OVERRIDE;
     virtual bool preventExtensions(JSContext *cx, HandleObject wrapper) MOZ_OVERRIDE;
     virtual bool enter(JSContext *cx, HandleObject wrapper, HandleId id, Wrapper::Action act,
                        bool *bp) MOZ_OVERRIDE;
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) MOZ_OVERRIDE;
     virtual bool defaultValue(JSContext *cx, HandleObject wrapper, JSType hint,
                               MutableHandleValue vp) MOZ_OVERRIDE;
     virtual bool objectClassIs(HandleObject obj, ESClassValue classValue,
@@ -180,17 +180,17 @@ class JS_FRIEND_API(DeadObjectProxy) : p
     virtual bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id,
                                 PropertyDescriptor *desc) MOZ_OVERRIDE;
     virtual bool getOwnPropertyNames(JSContext *cx, HandleObject wrapper,
                                      AutoIdVector &props) MOZ_OVERRIDE;
     virtual bool delete_(JSContext *cx, HandleObject wrapper, HandleId id, bool *bp) MOZ_OVERRIDE;
     virtual bool enumerate(JSContext *cx, HandleObject wrapper, AutoIdVector &props) MOZ_OVERRIDE;
 
     /* Spidermonkey extensions. */
-    virtual bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
+    virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) MOZ_OVERRIDE;
     virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) MOZ_OVERRIDE;
     virtual bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v,
                              bool *bp) MOZ_OVERRIDE;
     virtual bool objectClassIs(HandleObject obj, ESClassValue classValue,
                                JSContext *cx) MOZ_OVERRIDE;
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -4771,17 +4771,20 @@ DebuggerObject_sealHelper(JSContext *cx,
     ErrorCopier ec(ac, dbg->toJSObject());
     bool ok;
     if (op == Seal) {
         ok = JSObject::seal(cx, obj);
     } else if (op == Freeze) {
         ok = JSObject::freeze(cx, obj);
     } else {
         JS_ASSERT(op == PreventExtensions);
-        if (!obj->isExtensible()) {
+        bool extensible;
+        if (!JSObject::isExtensible(cx, obj, &extensible))
+            return false;
+        if (!extensible) {
             args.rval().setUndefined();
             return true;
         }
         ok = JSObject::preventExtensions(cx, obj);
     }
     if (!ok)
         return false;
     args.rval().setUndefined();
@@ -4818,17 +4821,18 @@ DebuggerObject_isSealedHelper(JSContext 
     bool r;
     if (op == Seal) {
         if (!JSObject::isSealed(cx, obj, &r))
             return false;
     } else if (op == Freeze) {
         if (!JSObject::isFrozen(cx, obj, &r))
             return false;
     } else {
-        r = obj->isExtensible();
+        if (!JSObject::isExtensible(cx, obj, &r))
+            return false;
     }
     args.rval().setBoolean(r);
     return true;
 }
 
 static JSBool
 DebuggerObject_isSealed(JSContext *cx, unsigned argc, Value *vp)
 {
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -119,17 +119,20 @@ ProtoSetterImpl(JSContext *cx, CallArgs 
     }
 
     if (!cx->runningWithTrustedPrincipals())
         ++sSetProtoCalled;
 
     Rooted<JSObject*> obj(cx, &args.thisv().toObject());
 
     /* ES5 8.6.2 forbids changing [[Prototype]] if not [[Extensible]]. */
-    if (!obj->isExtensible()) {
+    bool extensible;
+    if (!JSObject::isExtensible(cx, obj, &extensible))
+        return false;
+    if (!extensible) {
         obj->reportNotExtensible(cx);
         return false;
     }
 
     /*
      * Disallow mutating the [[Prototype]] of a proxy that wasn't simply
      * wrapping some other object.  Also disallow it on ArrayBuffer objects,
      * which due to their complicated delegate-object shenanigans can't easily
--- a/js/src/vm/ObjectImpl-inl.h
+++ b/js/src/vm/ObjectImpl-inl.h
@@ -34,25 +34,37 @@ js::ObjectImpl::nativeContains(JSContext
 
 inline bool
 js::ObjectImpl::nativeContainsPure(Shape *shape)
 {
     return nativeLookupPure(shape->propid()) == shape;
 }
 
 inline bool
-js::ObjectImpl::isExtensible() const
+js::ObjectImpl::nonProxyIsExtensible() const
 {
-    if (this->isProxy())
-        return Proxy::isExtensible(const_cast<JSObject*>(this->asObjectPtr()));
+    MOZ_ASSERT(!isProxy());
 
     // [[Extensible]] for ordinary non-proxy objects is an object flag.
     return !lastProperty()->hasObjectFlag(BaseShape::NOT_EXTENSIBLE);
 }
 
+/* static */ inline bool
+js::ObjectImpl::isExtensible(JSContext *cx, js::Handle<ObjectImpl*> obj, bool *extensible)
+{
+    if (obj->isProxy()) {
+        HandleObject h =
+            HandleObject::fromMarkedLocation(reinterpret_cast<JSObject* const*>(obj.address()));
+        return Proxy::isExtensible(cx, h, extensible);
+    }
+
+    *extensible = obj->nonProxyIsExtensible();
+    return true;
+}
+
 inline bool
 js::ObjectImpl::isNative() const
 {
     return lastProperty()->isNative();
 }
 
 inline bool
 js::ObjectImpl::isProxy() const
--- a/js/src/vm/ObjectImpl.cpp
+++ b/js/src/vm/ObjectImpl.cpp
@@ -476,17 +476,20 @@ DenseElementsHeader::defineElement(JSCon
             // XXX jwalden fill this in!
         }
     }
 
     /*
      * If the element doesn't exist, we can only add it if the object is
      * extensible.
      */
-    if (!obj->isExtensible()) {
+    bool extensible;
+    if (!JSObject::isExtensible(cx, obj, &extensible))
+        return false;
+    if (!extensible) {
         *succeeded = false;
         if (!shouldThrow)
             return true;
         RootedValue val(cx, ObjectValue(*obj));
         MOZ_ALWAYS_FALSE(js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_OBJECT_NOT_EXTENSIBLE,
                                                   JSDVG_IGNORE_STACK,
                                                   val, NullPtr(),
                                                   NULL, NULL));
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -1234,17 +1234,24 @@ class ObjectImpl : public gc::Cell
     JSObject * getProto() const {
         return type_->proto;
     }
 
     Class *getClass() const {
         return type_->clasp;
     }
 
-    inline bool isExtensible() const;
+    static inline bool
+    isExtensible(JSContext *cx, Handle<ObjectImpl*> obj, bool *extensible);
+
+    // Indicates whether a non-proxy is extensible.  Don't call on proxies!
+    // This method really shouldn't exist -- but there are a few internal
+    // places that want it (JITs and the like), and it'd be a pain to mark them
+    // all as friends.
+    inline bool nonProxyIsExtensible() const;
 
     // Attempt to change the [[Extensible]] bit on |obj| to false.  Callers
     // must ensure that |obj| is currently extensible before calling this!
     static bool
     preventExtensions(JSContext *cx, Handle<ObjectImpl*> obj);
 
     HeapSlotArray getDenseElements() {
         JS_ASSERT(uninlinedIsNative());
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -1353,20 +1353,21 @@ class DebugScopeProxy : public BaseProxy
     }
 
   public:
     static int family;
     static DebugScopeProxy singleton;
 
     DebugScopeProxy() : BaseProxyHandler(&family) {}
 
-    bool isExtensible(JSObject *proxy) MOZ_OVERRIDE
+    bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) MOZ_OVERRIDE
     {
         // always [[Extensible]], can't be made non-[[Extensible]], like most
         // proxies
+        *extensible = true;
         return true;
     }
 
     bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE
     {
         // See above.
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CHANGE_EXTENSIBILITY);
         return false;
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -441,17 +441,20 @@ NormalizeGetterAndSetter(JSObject *obj,
 /* static */ Shape *
 JSObject::addProperty(JSContext *cx, HandleObject obj, HandleId id,
                       PropertyOp getter, StrictPropertyOp setter,
                       uint32_t slot, unsigned attrs,
                       unsigned flags, int shortid, bool allowDictionary)
 {
     JS_ASSERT(!JSID_IS_VOID(id));
 
-    if (!obj->isExtensible()) {
+    bool extensible;
+    if (!JSObject::isExtensible(cx, obj, &extensible))
+        return NULL;
+    if (!extensible) {
         obj->reportNotExtensible(cx);
         return NULL;
     }
 
     NormalizeGetterAndSetter(obj, id, attrs, flags, getter, setter);
 
     Shape **spp = NULL;
     if (obj->inDictionaryMode())
@@ -605,17 +608,20 @@ JSObject::putProperty(JSContext *cx, Han
     /* Search for id in order to claim its entry if table has been allocated. */
     Shape **spp;
     RootedShape shape(cx, Shape::search(cx, obj->lastProperty(), id, &spp, true));
     if (!shape) {
         /*
          * You can't add properties to a non-extensible object, but you can change
          * attributes of properties in such objects.
          */
-        if (!obj->isExtensible()) {
+        bool extensible;
+        if (!JSObject::isExtensible(cx, obj, &extensible))
+            return NULL;
+        if (!extensible) {
             obj->reportNotExtensible(cx);
             return NULL;
         }
 
         return addPropertyInternal(cx, obj, id, getter, setter, slot, attrs, flags, shortid, spp, true);
     }
 
     /* Property exists: search must have returned a valid *spp. */
@@ -1072,19 +1078,24 @@ Shape::setObjectMetadata(JSContext *cx, 
 
     RootedShape lastRoot(cx, last);
     return replaceLastProperty(cx, base, proto, lastRoot);
 }
 
 /* static */ bool
 js::ObjectImpl::preventExtensions(JSContext *cx, Handle<ObjectImpl*> obj)
 {
-    MOZ_ASSERT(obj->isExtensible(),
+#ifdef DEBUG
+    bool extensible;
+    if (!JSObject::isExtensible(cx, obj, &extensible))
+        return false;
+    MOZ_ASSERT(extensible,
                "Callers must ensure |obj| is extensible before calling "
                "preventExtensions");
+#endif
 
     if (obj->isProxy()) {
         RootedObject object(cx, obj->asObjectPtr());
         return js::Proxy::preventExtensions(cx, object);
     }
 
     RootedObject self(cx, obj->asObjectPtr());
 
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -1301,23 +1301,24 @@ DEBUG_CheckXBLLookup(JSContext *cx, JSPr
     }
 }
 #else
 #define DEBUG_CheckXBLLookup(a, b) {}
 #endif
 
 template <typename Base, typename Traits>
 bool
-XrayWrapper<Base, Traits>::isExtensible(JSObject *wrapper)
+XrayWrapper<Base, Traits>::isExtensible(JSContext *cx, JS::Handle<JSObject*> wrapper, bool *extensible)
 {
     // Xray wrappers are supposed to provide a clean view of the target
     // reflector, hiding any modifications by script in the target scope.  So
     // even if that script freezes the reflector, we don't want to make that
     // visible to the caller. DOM reflectors are always extensible by default,
     // so we can just return true here.
+    *extensible = true;
     return true;
 }
 
 template <typename Base, typename Traits>
 bool
 XrayWrapper<Base, Traits>::preventExtensions(JSContext *cx, HandleObject wrapper)
 {
     // See above.
--- a/js/xpconnect/wrappers/XrayWrapper.h
+++ b/js/xpconnect/wrappers/XrayWrapper.h
@@ -65,17 +65,17 @@ XrayTraits* GetXrayTraits(JSObject *obj)
 // NB: Base *must* derive from JSProxyHandler
 template <typename Base, typename Traits = XPCWrappedNativeXrayTraits >
 class XrayWrapper : public Base {
   public:
     XrayWrapper(unsigned flags);
     virtual ~XrayWrapper();
 
     /* Fundamental proxy traps. */
-    virtual bool isExtensible(JSObject *wrapper) MOZ_OVERRIDE;
+    virtual bool isExtensible(JSContext *cx, JS::Handle<JSObject*> wrapper, bool *extensible) MOZ_OVERRIDE;
     virtual bool preventExtensions(JSContext *cx, JS::Handle<JSObject*> wrapper) MOZ_OVERRIDE;
     virtual bool getPropertyDescriptor(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
                                        js::PropertyDescriptor *desc, unsigned flags);
     virtual bool getOwnPropertyDescriptor(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
                                           js::PropertyDescriptor *desc,
                                           unsigned flags);
     virtual bool defineProperty(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
                                 js::PropertyDescriptor *desc);