[INFER] Don't collect type objects with unknown properties for mutable __proto__, bug 639126.
authorBrian Hackett <bhackett1024@gmail.com>
Sat, 05 Mar 2011 21:23:02 -0800
changeset 74718 855c0198a515534a91ffda1084c015c8cd09f280
parent 74717 89f6b05273e846ac63968037a1259c19ae29b712
child 74719 a0812f46f7ba52fbc3fbae46ab41762801cf6071
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
bugs639126
milestone2.0b12pre
[INFER] Don't collect type objects with unknown properties for mutable __proto__, bug 639126.
js/src/jit-test/tests/basic/bug639126.js
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
js/src/jsobj.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug639126.js
@@ -0,0 +1,4 @@
+Array.__proto__ = Array.__proto__;
+gc();
+Array["name" + ""];
+Array();
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -930,17 +930,17 @@ TypeConstraintCall::newType(JSContext *c
         cx->compartment->types.monitorBytecode(cx, script, pc - script->code);
         return;
     }
 
     /* Get the function being invoked. */
     TypeFunction *function = NULL;
     if (TypeIsObject(type)) {
         TypeObject *object = (TypeObject*) type;
-        if (object->isFunction) {
+        if (object->isFunction && !object->unknownProperties) {
             function = (TypeFunction*) object;
         } else {
             /* Unknown return value for calls on non-function objects. */
             cx->compartment->types.monitorBytecode(cx, script, pc - script->code);
         }
     }
     if (!function)
         return;
@@ -1297,16 +1297,24 @@ TypeSet::getKnownTypeTag(JSContext *cx, 
 
     return type;
 }
 
 /* Compute the meet of kind with the kind of object, per the ObjectKind lattice. */
 static inline ObjectKind
 CombineObjectKind(TypeObject *object, ObjectKind kind)
 {
+    /*
+     * All type objects with unknown properties are considered interchangeable
+     * with one another, as they can be freely exchanged in type sets to handle
+     * objects whose __proto__ has been changed.
+     */
+    if (object->unknownProperties)
+        return OBJECT_UNKNOWN;
+
     ObjectKind nkind;
     if (object->isFunction)
         nkind = object->asFunction()->script ? OBJECT_SCRIPTED_FUNCTION : OBJECT_NATIVE_FUNCTION;
     else if (object->isPackedArray)
         nkind = OBJECT_PACKED_ARRAY;
     else if (object->isDenseArray)
         nkind = OBJECT_DENSE_ARRAY;
     else
@@ -3791,18 +3799,32 @@ CondenseSweepTypeSet(JSContext *cx, Type
     JS_ASSERT(!(types->typeFlags & TYPE_FLAG_INTERMEDIATE_SET));
 
     if (types->objectCount >= 2) {
         bool removed = false;
         unsigned objectCapacity = HashSetCapacity(types->objectCount);
         for (unsigned i = 0; i < objectCapacity; i++) {
             TypeObject *object = types->objectSet[i];
             if (object && !object->marked) {
+                /*
+                 * If the object has unknown properties, instead of removing it
+                 * replace it with the compartment's empty type object. This is
+                 * needed to handle mutable __proto__ --- the type object in
+                 * the set may no longer be used but there could be a JSObject
+                 * which originally had the type and was changed to a different
+                 * type object with unknown properties.
+                 */
+                if (object->unknownProperties) {
+                    types->objectSet[i] = cx->getTypeEmpty();
+                    if (!types->objectSet[i])
+                        compartment->setPendingNukeTypes(cx);
+                } else {
+                    types->objectSet[i] = NULL;
+                }
                 removed = true;
-                types->objectSet[i] = NULL;
             }
         }
         if (removed) {
             /* Reconstruct the type set to re-resolve hash collisions. */
             TypeObject **oldArray = types->objectSet;
             types->objectSet = NULL;
             types->objectCount = 0;
             for (unsigned i = 0; i < objectCapacity; i++) {
@@ -3814,18 +3836,24 @@ CondenseSweepTypeSet(JSContext *cx, Type
                         *pentry = object;
                 }
             }
             cx->free(oldArray);
         }
     } else if (types->objectCount == 1) {
         TypeObject *object = (TypeObject*) types->objectSet;
         if (!object->marked) {
-            types->objectSet = NULL;
-            types->objectCount = 0;
+            if (object->unknownProperties) {
+                types->objectSet = (TypeObject**) cx->getTypeEmpty();
+                if (!types->objectSet)
+                    compartment->setPendingNukeTypes(cx);
+            } else {
+                types->objectSet = NULL;
+                types->objectCount = 0;
+            }
         }
     }
 
     TypeConstraint *constraint = types->constraintList;
     types->constraintList = NULL;
 
     /*
      * Keep track of all the scripts we have found or generated
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -378,17 +378,23 @@ struct TypeObject
     TypeObject *instanceNext;
 
     /*
      * Link in the list of objects associated with a script or global object.
      * For printing and tracking initializer objects (remove?).
      */
     TypeObject *next;
 
-    /* Whether all the properties of this object are unknown. */
+    /*
+     * Whether all the properties of this object are unknown. When this object
+     * appears in a type set, nothing can be assumed about its contents, including
+     * whether the .proto field is correct. This is needed to handle mutable
+     * __proto__, which requires us to unify all type objects with unknown
+     * properties in type sets (see SetProto).
+     */
     bool unknownProperties;
 
     /* Whether all objects this represents are dense arrays. */
     bool isDenseArray;
 
     /* Whether all objects this represents are packed arrays (implies isDenseArray). */
     bool isPackedArray;
 
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -311,18 +311,21 @@ JSContext::addTypePropertyId(js::types::
     if (typeInferenceEnabled() && !obj->unknownProperties)
         return addTypePropertyId(obj, id, js::types::GetValueType(this, value));
     return true;
 }
 
 inline js::types::TypeObject *
 JSContext::getTypeEmpty()
 {
-    if (!compartment->types.typeEmpty)
+    if (!compartment->types.typeEmpty) {
         compartment->types.typeEmpty = newTypeObject("Empty", NULL);
+        if (compartment->types.typeEmpty)
+            compartment->types.typeEmpty->unknownProperties = true;
+    }
     return compartment->types.typeEmpty;
 }
 
 inline js::types::TypeObject *
 JSContext::getTypeGetSet()
 {
     if (!compartment->types.typeGetSet)
         compartment->types.typeGetSet = newTypeObject("GetSet", NULL);
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -4283,16 +4283,19 @@ SetProto(JSContext *cx, JSObject *obj, J
 
     TypeObject *type = proto ? proto->getNewType(cx) : cx->getTypeEmpty();
     if (!type)
         return false;
 
     /*
      * Setting __proto__ on an object that has escaped and may be referenced by
      * other heap objects can only be done if the properties of both objects are unknown.
+     * Type sets containing this object will contain the original type but not the
+     * new type of the object, which is OK since we treat objects in type sets with
+     * unknown properties as interchangeable.
      */
     if (!cx->markTypeObjectUnknownProperties(obj->getType()) ||
         !cx->markTypeObjectUnknownProperties(type)) {
         return false;
     }
 
     if (!proto || !checkForCycles) {
         obj->setType(type);