Bug 611653: clear values instead of scope when global window shuts down, r=brendan,smaug
☠☠ backed out by 36a4ecbcb4be ☠ ☠
authorDavid Mandelin <dmandelin@mozilla.com>
Fri, 21 Jan 2011 18:37:30 -0800
changeset 61446 b5ca98debed07f8bf861c23cb80e49c0323bc5de
parent 61445 b0ae8b5a88a036df4d6596d3a795fba8c3d39f72
child 61447 53dea513311e6b915b229cf2f8d784b3cc482559
child 61456 36a4ecbcb4be58b3bc1f9ae208069b756faa2a3f
push idunknown
push userunknown
push dateunknown
reviewersbrendan, smaug
bugs611653
milestone2.0b10pre
Bug 611653: clear values instead of scope when global window shuts down, r=brendan,smaug
dom/base/nsJSEnvironment.cpp
js/src/jsapi.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsscope.h
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -3237,17 +3237,28 @@ nsJSContext::ClearScope(void *aGlobalObj
     // nothing else does.
     jsval window;
     if (!JS_GetProperty(mContext, obj, "window", &window)) {
       window = JSVAL_VOID;
 
       JS_ClearPendingException(mContext);
     }
 
-    JS_ClearScope(mContext, obj);
+    // Hack fix for bug 611653. Originally, this always called JS_ClearScope,
+    // which was required to avoid leaks. But for native objects, the JS
+    // engine has an optimization that requires that permanent properties of
+    // the global object are never deleted. So instead, we call a new special
+    // API that clears the values of the global, thus avoiding leaks without
+    // deleting any properties.
+    if (obj->isNative()) {
+      js_UnbrandAndClearSlots(mContext, obj);
+    } else {
+      JS_ClearScope(mContext, obj);
+    }
+
     if (xpc::WrapperFactory::IsXrayWrapper(obj)) {
       JS_ClearScope(mContext, &obj->getProxyExtra().toObject());
     }
 
     if (window != JSVAL_VOID) {
       if (!JS_DefineProperty(mContext, obj, "window", window,
                              JS_PropertyStub, JS_PropertyStub,
                              JSPROP_ENUMERATE | JSPROP_READONLY |
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1954,17 +1954,17 @@ struct JSClass {
  * deleteable, for the most part.
  *
  * Implementing this efficiently requires that global objects have classes
  * with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
  * prevously allowed, but is now an ES5 violation and thus unsupported.
  */
 #define JSCLASS_GLOBAL_FLAGS                                                  \
     (JSCLASS_IS_GLOBAL |                                                      \
-     JSCLASS_HAS_RESERVED_SLOTS(JSProto_LIMIT * 3 + JSRESERVED_GLOBAL_SLOTS_COUNT))
+     JSCLASS_HAS_RESERVED_SLOTS(JSRESERVED_GLOBAL_THIS + JSRESERVED_GLOBAL_SLOTS_COUNT))
 
 /* Fast access to the original value of each standard class's prototype. */
 #define JSCLASS_CACHED_PROTO_SHIFT      (JSCLASS_HIGH_FLAGS_SHIFT + 8)
 #define JSCLASS_CACHED_PROTO_WIDTH      8
 #define JSCLASS_CACHED_PROTO_MASK       JS_BITMASK(JSCLASS_CACHED_PROTO_WIDTH)
 #define JSCLASS_HAS_CACHED_PROTO(key)   ((key) << JSCLASS_CACHED_PROTO_SHIFT)
 #define JSCLASS_CACHED_PROTO_KEY(clasp) ((JSProtoKey)                         \
                                          (((clasp)->flags                     \
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -4387,16 +4387,52 @@ JSObject::freeSlot(JSContext *cx, uint32
             last = slot;
             return true;
         }
     }
     vref.setUndefined();
     return false;
 }
 
+JS_FRIEND_API(bool)
+js_UnbrandAndClearSlots(JSContext *cx, JSObject *obj)
+{
+    JS_ASSERT(obj->isNative());
+    JS_ASSERT(obj->isGlobal());
+
+    if (!obj->unbrand(cx))
+        return false;
+
+    /*
+     * Clear the prototype cache. We must not clear the other global
+     * reserved slots, as other code will crash if they are arbitrarily
+     * reset (e.g., regexp statics).
+     */
+    for (int key = JSProto_Null; key < JSRESERVED_GLOBAL_THIS; key++)
+        JS_SetReservedSlot(cx, obj, key, JSVAL_VOID);
+
+    /*
+     * Clear the non-reserved slots.
+     */
+    ClearValueRange(obj->slots + JSCLASS_RESERVED_SLOTS(obj->clasp),
+                    obj->capacity - JSCLASS_RESERVED_SLOTS(obj->clasp),
+                    obj->clasp == &js_ArrayClass);
+
+    /*
+     * We just overwrote all slots to undefined, so the freelist has
+     * been trashed. We need to clear the head pointer or else we will
+     * crash later. This leaks slots but the object is all but dead
+     * anyway.
+     */
+    if (obj->hasPropertyTable())
+        obj->lastProperty()->table->freelist = SHAPE_INVALID_SLOT;
+
+    return true;
+}
+
 /* JSBOXEDWORD_INT_MAX as a string */
 #define JSBOXEDWORD_INT_MAX_STRING "1073741823"
 
 /*
  * Convert string indexes that convert to int jsvals as ints to save memory.
  * Care must be taken to use this macro every time a property name is used, or
  * else double-sets, incorrect property cache misses, or other mistakes could
  * occur.
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1718,16 +1718,25 @@ js_SetPropertyHelper(JSContext *cx, JSOb
 /*
  * Change attributes for the given native property. The caller must ensure
  * that obj is locked and this function always unlocks obj on return.
  */
 extern JSBool
 js_SetNativeAttributes(JSContext *cx, JSObject *obj, js::Shape *shape,
                        uintN attrs);
 
+/*
+ * Hack fix for bug 611653: Do not use for any other purpose.
+ *
+ * Unbrand and set all slot values to undefined (except reserved slots that
+ * are not used for cached prototypes).
+ */
+JS_FRIEND_API(bool)
+js_UnbrandAndClearSlots(JSContext *cx, JSObject *obj);
+
 namespace js {
 
 /*
  * If obj has a data property methodid which is a function object for the given
  * native, return that function object. Otherwise, return NULL.
  */
 extern JSObject *
 HasNativeMethod(JSObject *obj, jsid methodid, Native native);
--- a/js/src/jsscope.h
+++ b/js/src/jsscope.h
@@ -292,16 +292,17 @@ CastAsPropertyOp(js::Class *clasp)
 
 struct Shape : public JSObjectMap
 {
     friend struct ::JSObject;
     friend struct ::JSFunction;
     friend class js::PropertyTree;
     friend class js::Bindings;
     friend bool IsShapeAboutToBeFinalized(JSContext *cx, const js::Shape *shape);
+    friend JS_FRIEND_API(bool) ::js_UnbrandAndClearSlots(JSContext *cx, JSObject *obj);
 
   protected:
     mutable uint32 numSearches;     /* Only updated until it reaches HASH_MIN_SEARCHES. */
     mutable js::PropertyTable *table;
 
   public:
     inline void freeTable(JSContext *cx);