[INFER] Fixes for jstests, bug 620599, part 1.
authorBrian Hackett <bhackett1024@gmail.com>
Tue, 21 Dec 2010 07:32:21 -0800
changeset 74681 513c680568de0a894f1275635733b9df19cf78a2
parent 74680 fce4c50e6f4947a39ae99fcb73c645cce8cf0b11
child 74682 a6438d91ca4d309bf5356eed2dc799608379f924
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
bugs620599
milestone2.0b8pre
[INFER] Fixes for jstests, bug 620599, part 1.
js/src/jit-test/tests/basic/arrayProto.js
js/src/jit-test/tests/basic/primitiveProto.js
js/src/jsapi.cpp
js/src/jsarray.cpp
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
js/src/jsobj.cpp
js/src/jsxml.cpp
js/src/methodjit/PolyIC.cpp
js/src/methodjit/StubCalls.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/arrayProto.js
@@ -0,0 +1,12 @@
+
+for (var i = 0; i < 15; i++) {
+  var x = Object.create([]);
+  assertEq(x.length, 0);
+}
+
+for (var i = 0; i < 15; i++) {
+  function foo() {}
+  foo.prototype = [];
+  var x = new foo();
+  assertEq(x.length, 0);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/primitiveProto.js
@@ -0,0 +1,24 @@
+
+function fna() {}
+fna.prototype = undefined;
+new fna;
+
+function fnb() {}
+fnb.prototype = null;
+new fnb;
+
+function fnc() {}
+fnc.prototype = 3;
+new fnc;
+
+function fnd() {}
+fnd.prototype = true;
+new fnd;
+
+function fne() {}
+fne.prototype = "foo";
+new fne;
+
+function fnf() {}
+fnf.prototype = /foo/;
+new fnf;
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3050,18 +3050,20 @@ JS_NewObject(JSContext *cx, JSClass *jsc
     Class *clasp = Valueify(jsclasp);
     if (!clasp)
         clasp = &js_ObjectClass;    /* default class is Object */
 
     JS_ASSERT(clasp != &js_FunctionClass);
     JS_ASSERT(!(clasp->flags & JSCLASS_IS_GLOBAL));
 
     JSObject *obj = NewNonFunction<WithProto::Class>(cx, clasp, proto, parent);
-    if (obj)
+    if (obj) {
         obj->syncSpecialEquality();
+        cx->markTypeObjectUnknownProperties(obj->getType());
+    }
 
     JS_ASSERT_IF(obj, obj->getParent());
     return obj;
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_NewObjectWithGivenProto(JSContext *cx, JSClass *jsclasp, JSObject *proto, JSObject *parent)
 {
@@ -3072,18 +3074,20 @@ JS_NewObjectWithGivenProto(JSContext *cx
     Class *clasp = Valueify(jsclasp);
     if (!clasp)
         clasp = &js_ObjectClass;    /* default class is Object */
 
     JS_ASSERT(clasp != &js_FunctionClass);
     JS_ASSERT(!(clasp->flags & JSCLASS_IS_GLOBAL));
 
     JSObject *obj = NewNonFunction<WithProto::Given>(cx, clasp, proto, parent);
-    if (obj)
+    if (obj) {
         obj->syncSpecialEquality();
+        cx->markTypeObjectUnknownProperties(obj->getType());
+    }
     return obj;
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_NewObjectForConstructor(JSContext *cx, const jsval *vp)
 {
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, *vp);
@@ -4423,17 +4427,17 @@ JS_TypeHandlerNew(JSContext *cx, JSTypeF
     TypeFunction *fun = Valueify(jsfun);
     TypeCallsite *site = Valueify(jssite);
 
     if (!site->returnTypes)
         return;
 
     TypeSet *prototypeTypes =
         fun->getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom), true);
-    prototypeTypes->addNewObject(cx, *fun->pool, site->returnTypes);
+    prototypeTypes->addNewObject(cx, fun, site->returnTypes);
 #endif
 }
 
 JS_PUBLIC_API(void)
 JS_TypeHandlerThis(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
 #ifdef JS_TYPE_INFERENCE
     TypeCallsite *site = Valueify(jssite);
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -3361,16 +3361,20 @@ js_Array(JSContext *cx, uintN argc, Valu
 
     if (!obj)
         return JS_FALSE;
 
     obj->setType(type);
     if (cx->isTypeCallerMonitored())
         cx->markTypeObjectUnknownProperties(type);
 
+    /* If the length calculation overflowed, make sure that is marked for the new type. */
+    if (obj->getArrayLength() > INT32_MAX)
+        obj->setArrayLength(cx, obj->getArrayLength());
+
     vp->setObject(*obj);
     return JS_TRUE;
 }
 
 // specialized handler for Array() that propagates arguments into indexes
 // of the resulting array.
 static void array_TypeNew(JSContext *cx, JSTypeFunction *jsfun, JSTypeCallsite *jssite)
 {
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -400,31 +400,32 @@ TypeSet::addSetElem(JSContext *cx, analy
 {
     JS_ASSERT(this->pool == &code->pool());
     add(cx, ArenaNew<TypeConstraintElem>(code->pool(), code, object, target, true));
 }
 
 /* Constraints for determining the 'this' object at sites invoked using 'new'. */
 class TypeConstraintNewObject : public TypeConstraint
 {
+    TypeFunction *fun;
     TypeSet *target;
 
   public:
-    TypeConstraintNewObject(TypeSet *target)
-        : TypeConstraint("newObject"), target(target)
+    TypeConstraintNewObject(TypeFunction *fun, TypeSet *target)
+        : TypeConstraint("newObject"), fun(fun), target(target)
     {}
 
     void newType(JSContext *cx, TypeSet *source, jstype type);
 };
 
 void
-TypeSet::addNewObject(JSContext *cx, JSArenaPool &pool, TypeSet *target)
+TypeSet::addNewObject(JSContext *cx, TypeFunction *fun, TypeSet *target)
 {
-    JS_ASSERT(this->pool == &pool);
-    add(cx, ArenaNew<TypeConstraintNewObject>(pool, target));
+    JS_ASSERT(this->pool == fun->pool);
+    add(cx, ArenaNew<TypeConstraintNewObject>(*fun->pool, fun, target));
 }
 
 /*
  * Constraints for watching call edges as they are discovered and invoking native
  * function handlers, adding constraints for arguments, receiver objects and the
  * return value, and updating script foundOffsets.
  */
 class TypeConstraintCall : public TypeConstraint
@@ -732,21 +733,29 @@ TypeConstraintElem::newType(JSContext *c
 void
 TypeConstraintNewObject::newType(JSContext *cx, TypeSet *source, jstype type)
 {
     if (type == TYPE_UNKNOWN) {
         target->addType(cx, TYPE_UNKNOWN);
         return;
     }
 
-    /* :FIXME: Handle non-object prototype case dynamically. */
     if (TypeIsObject(type)) {
         TypeObject *object = (TypeObject *) type;
         TypeSet *newTypes = object->getProperty(cx, JSID_EMPTY, true);
         newTypes->addSubset(cx, *object->pool, target);
+    } else if (!fun->script) {
+        /*
+         * This constraint should only be used for native constructors with
+         * immutable non-primitive prototypes. Disregard primitives here.
+         */
+    } else if (!fun->script->compileAndGo) {
+        target->addType(cx, TYPE_UNKNOWN);
+    } else {
+        target->addType(cx, (jstype) cx->getTypeNewObject(JSProto_Object));
     }
 }
 
 void
 TypeConstraintCall::newType(JSContext *cx, TypeSet *source, jstype type)
 {
     if (type == TYPE_UNKNOWN) {
         /* Monitor calls on unknown functions. */
@@ -836,17 +845,17 @@ TypeConstraintCall::newType(JSContext *c
 
     /* Add a binding for the receiver object of the call. */
     if (callsite->isNew) {
         /* The receiver object is the 'new' object for the function's prototype. */
         if (function->unknownProperties) {
             script->thisTypes.addType(cx, TYPE_UNKNOWN);
         } else {
             TypeSet *prototypeTypes = function->getProperty(cx, id_prototype(cx), false);
-            prototypeTypes->addNewObject(cx, *function->pool, &script->thisTypes);
+            prototypeTypes->addNewObject(cx, function, &script->thisTypes);
         }
 
         /*
          * If the script does not return a value then the pushed value is the new
          * object (typical case).
          */
         if (callsite->returnTypes) {
             script->thisTypes.addSubset(cx, script->pool, callsite->returnTypes);
@@ -1295,33 +1304,50 @@ TypeSet::knownNonEmpty(JSContext *cx, JS
     if (typeFlags != 0)
         return true;
 
     add(cx, ArenaNew<TypeConstraintFreezeNonEmpty>(script->analysis->pool, script), false);
 
     return false;
 }
 
+static bool
+HasUnknownObject(TypeSet *types)
+{
+    if (types->objectCount >= 2) {
+        unsigned objectCapacity = HashSetCapacity(types->objectCount);
+        for (unsigned i = 0; i < objectCapacity; i++) {
+            TypeObject *object = types->objectSet[i];
+            if (object && object->unknownProperties)
+                return true;
+        }
+    } else if (types->objectCount == 1 && ((TypeObject*)types->objectSet)->unknownProperties) {
+        return true;
+    }
+    return false;
+}
+
 /////////////////////////////////////////////////////////////////////
 // TypeCompartment
 /////////////////////////////////////////////////////////////////////
 
 void 
 TypeCompartment::growPendingArray()
 {
     pendingCapacity = js::Max(unsigned(100), pendingCapacity * 2);
     PendingWork *oldArray = pendingArray;
     pendingArray = ArenaArray<PendingWork>(pool, pendingCapacity);
     memcpy(pendingArray, oldArray, pendingCount * sizeof(PendingWork));
 }
 
 void
 TypeCompartment::addDynamicType(JSContext *cx, TypeSet *types, jstype type)
 {
-    JS_ASSERT(!types->hasType(type));
+    JS_ASSERT_IF(type == TYPE_UNKNOWN, !types->unknown());
+    JS_ASSERT_IF(type != TYPE_UNKNOWN, !types->hasType(type));
 
     interpreting = false;
     uint64_t startTime = currentTime();
 
     types->addType(cx, type);
 
     uint64_t endTime = currentTime();
     analysisTime += (endTime - startTime);
@@ -1477,16 +1503,17 @@ TypeCompartment::monitorBytecode(JSConte
      * Make sure monitoring is limited to property sets and calls where the
      * target of the set/call could be statically unknown, and mark the bytecode
      * results as unknown.
      */
     JSOp op = JSOp(code->script->getScript()->code[code->offset]);
     switch (op) {
       case JSOP_SETNAME:
       case JSOP_SETGNAME:
+      case JSOP_SETXMLNAME:
       case JSOP_SETELEM:
       case JSOP_SETPROP:
       case JSOP_SETMETHOD:
       case JSOP_INITPROP:
       case JSOP_INITMETHOD:
       case JSOP_FORPROP:
       case JSOP_FORNAME:
       case JSOP_ENUMELEM:
@@ -1813,20 +1840,31 @@ JSScript::typeCheckBytecode(JSContext *c
         bool ignore = val.isMagic(JS_ARRAY_HOLE) || stack->ignoreTypeTag || IgnorePopped(op, i);
 
         stack = stack->innerStack ? stack->innerStack->group() : NULL;
 
         if (ignore)
             continue;
 
         js::types::jstype type = js::types::GetValueType(cx, val);
+
+        /*
+         * If this is a type for an object with unknown properties, match any object
+         * in the type set which also has unknown properties. This avoids failure
+         * on objects whose prototype (and thus type) changes dynamically, which will
+         * mark the old and new type objects as unknown.
+         */
+        if (js::types::TypeIsObject(type) && ((js::types::TypeObject*)type)->unknownProperties &&
+            js::types::HasUnknownObject(types)) {
+            continue;
+        }
+
         if (!types->hasType(type)) {
             js::types::TypeFailure(cx, "Missing type at #%u:%05u popped %u: %s",
                                    analysis->id, code.offset, i, js::types::TypeString(type));
-            return;
         }
 
         if (js::types::TypeIsObject(type)) {
             JS_ASSERT(val.isObject());
             JSObject *obj = &val.toObject();
             js::types::TypeObject *object = (js::types::TypeObject *) type;
 
             if (object->unknownProperties) {
@@ -2325,22 +2363,21 @@ Script::analyzeTypes(JSContext *cx, Byte
         Script *scope;
 
         switch (op) {
           case JSOP_GETGLOBAL:
           case JSOP_CALLGLOBAL:
             id = GetGlobalId(cx, this, pc);
             scope = SCOPE_GLOBAL;
             break;
-          case JSOP_GETGNAME:
-          case JSOP_CALLGNAME:
-            id = GetAtomId(cx, this, pc, 0);
-            scope = SCOPE_GLOBAL;
-            break;
           default:
+            /*
+             * Search the scope to mirror the interpreter's behavior, and to workaround
+             * cases where GNAME is broken, :FIXME: bug 605200.
+             */
             id = GetAtomId(cx, this, pc, 0);
             scope = SearchScope(cx, this, code->inStack, id);
             break;
         }
 
         if (scope == SCOPE_GLOBAL) {
             /*
              * This might be a lazily loaded property of the global object.
@@ -2405,20 +2442,25 @@ Script::analyzeTypes(JSContext *cx, Byte
             types->addSubset(cx, stack.scope->pool, code->pushed(0));
         } else {
             code->setFixed(cx, 0, TYPE_UNKNOWN);
         }
 
         break;
       }
 
+      case JSOP_INCGNAME:
+      case JSOP_DECGNAME:
+      case JSOP_GNAMEINC:
+      case JSOP_GNAMEDEC:
       case JSOP_INCNAME:
       case JSOP_DECNAME:
       case JSOP_NAMEINC:
       case JSOP_NAMEDEC: {
+        /* Same issue for searching scope as JSOP_GNAME/CALLGNAME. :FIXME: bug 605200 */
         jsid id = GetAtomId(cx, this, pc, 0);
 
         Script *scope = SearchScope(cx, this, code->inStack, id);
         if (scope == SCOPE_GLOBAL) {
             PropertyAccess(cx, code, cx->globalTypeObject(), true, NULL, id);
             PropertyAccess(cx, code, cx->globalTypeObject(), false, code->pushed(0), id);
         } else if (scope) {
             TypeSet *types = scope->getVariable(cx, id);
@@ -2457,26 +2499,16 @@ Script::analyzeTypes(JSContext *cx, Byte
         TypeSet *types = cx->globalTypeObject()->getProperty(cx, id, true);
         types->addArith(cx, cx->compartment->types.pool, code, types);
         MergePushed(cx, cx->compartment->types.pool, code, 0, types);
         if (code->hasIncDecOverflow)
             types->addType(cx, TYPE_DOUBLE);
         break;
       }
 
-      case JSOP_INCGNAME:
-      case JSOP_DECGNAME:
-      case JSOP_GNAMEINC:
-      case JSOP_GNAMEDEC: {
-        jsid id = GetAtomId(cx, this, pc, 0);
-        PropertyAccess(cx, code, cx->globalTypeObject(), true, NULL, id);
-        PropertyAccess(cx, code, cx->globalTypeObject(), false, code->pushed(0), id);
-        break;
-      }
-
       case JSOP_GETUPVAR:
       case JSOP_CALLUPVAR:
       case JSOP_GETFCSLOT:
       case JSOP_CALLFCSLOT: {
         unsigned index = GET_UINT16(pc);
 
         jsid id = JSID_VOID;
         Script *newScript = GetUpvarVariable(cx, code, index, &id);
@@ -3035,16 +3067,17 @@ Script::analyzeTypes(JSContext *cx, Byte
         code->setFixed(cx, 1, TYPE_UNKNOWN);
         /* FALLTHROUGH */
       case JSOP_XMLNAME:
         code->setFixed(cx, 0, TYPE_UNKNOWN);
         break;
 
       case JSOP_SETXMLNAME:
         cx->compartment->types.monitorBytecode(cx, code);
+        MergePushed(cx, pool, code, 0, code->popped(0));
         break;
 
       case JSOP_BINDXMLNAME:
         break;
 
       case JSOP_TOXML:
       case JSOP_TOXMLLIST:
       case JSOP_XMLPI:
@@ -3058,18 +3091,16 @@ Script::analyzeTypes(JSContext *cx, Byte
       case JSOP_GETFUNNS:
         code->setFixed(cx, 0, TYPE_UNKNOWN);
         break;
 
       case JSOP_FILTER:
         /* Note: the second value pushed by filter is a hole, and not modelled. */
         MergePushed(cx, pool, code, 0, code->popped(0));
         code->pushedArray[0].group()->boundWith = true;
-
-        /* Name binding inside filters is currently broken :FIXME: bug 605200. */
         break;
 
       case JSOP_ENDFILTER:
         MergePushed(cx, pool, code, 0, code->popped(1));
         break;
 
       case JSOP_DEFSHARP: {
         /*
@@ -3129,23 +3160,19 @@ Script::analyzeTypes(JSContext *cx, Byte
     } else {
         unsigned nuses = GetUseCount(script, offset);
         unsigned ndefs = GetDefCount(script, offset);
         memset(&state.stack[code->stackDepth - nuses], 0, ndefs * sizeof(AnalyzeStateStack));
         state.stackDepth = code->stackDepth - nuses + ndefs;
     }
 
     switch (op) {
-      case JSOP_BINDGNAME: {
-        AnalyzeStateStack &stack = state.popped(0);
-        stack.scope = SCOPE_GLOBAL;
-        break;
-      }
-
+      case JSOP_BINDGNAME:
       case JSOP_BINDNAME: {
+        /* Same issue for searching scope as JSOP_GNAME/JSOP_CALLGNAME. :FIXME: bug 605200 */
         jsid id = GetAtomId(cx, this, pc, 0);
         AnalyzeStateStack &stack = state.popped(0);
         stack.scope = SearchScope(cx, this, code->inStack, id);
         break;
       }
 
       case JSOP_ITER: {
         uintN flags = pc[1];
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -229,30 +229,32 @@ struct TypeSet
 #endif
     }
 
     void print(JSContext *cx);
 
     /* Whether this set contains a specific type. */
     inline bool hasType(jstype type);
 
+    bool unknown() { return typeFlags & TYPE_FLAG_UNKNOWN; }
+
     /*
      * Add a type to this set, calling any constraint handlers if this is a new
      * possible type.
      */
     inline void addType(JSContext *cx, jstype type);
 
     /* Add specific kinds of constraints to this set. */
     inline void add(JSContext *cx, TypeConstraint *constraint, bool callExisting = true);
     void addSubset(JSContext *cx, JSArenaPool &pool, TypeSet *target);
     void addGetProperty(JSContext *cx, analyze::Bytecode *code, TypeSet *target, jsid id);
     void addSetProperty(JSContext *cx, analyze::Bytecode *code, TypeSet *target, jsid id);
     void addGetElem(JSContext *cx, analyze::Bytecode *code, TypeSet *object, TypeSet *target);
     void addSetElem(JSContext *cx, analyze::Bytecode *code, TypeSet *object, TypeSet *target);
-    void addNewObject(JSContext *cx, JSArenaPool &pool, TypeSet *target);
+    void addNewObject(JSContext *cx, TypeFunction *fun, TypeSet *target);
     void addCall(JSContext *cx, TypeCallsite *site);
     void addArith(JSContext *cx, JSArenaPool &pool, analyze::Bytecode *code,
                   TypeSet *target, TypeSet *other = NULL);
     void addTransformThis(JSContext *cx, analyze::Bytecode *code, TypeSet *target);
     void addFilterPrimitives(JSContext *cx, JSArenaPool &pool, TypeSet *target, bool onlyNullVoid);
     void addMonitorRead(JSContext *cx, JSArenaPool &pool, analyze::Bytecode *code, TypeSet *target);
 
     /*
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -353,42 +353,42 @@ JSContext::typeMonitorCall(JSScript *cal
     if (!args.callee().isObject() || !args.callee().toObject().isFunction())
         return;
     JSFunction *callee = args.callee().toObject().getFunctionPrivate();
 
     /*
      * Don't do anything on calls to native functions.  If the call is monitored
      * then the return value is unknown, and when cx->isTypeCallerMonitored() natives
      * should inform inference of any side effects not on the return value.
+     * :FIXME: bug 619693 audit to make sure they do.
      */
     if (!callee->isInterpreted())
         return;
 
     if (!force) {
         if (caller->analysis->failed() || caller->analysis->getCode(callerpc).monitorNeeded)
             force = true;
     }
 
     typeMonitorEntry(callee->script());
 
     /* Don't need to do anything if this is at a non-monitored callsite. */
     if (!force)
         return;
 
-    js::types::jstype type;
-    if (constructing) {
-        /* Don't duplicate the logic in js_CreateThis, just mark 'this' as unknown. */
-        type = js::types::TYPE_UNKNOWN;
-    } else {
-        type = js::types::GetValueType(this, args.thisv());
-    }
-
     js::analyze::Script *script = callee->script()->analysis;
 
-    if (!constructing) {
+    if (constructing) {
+        if (!script->thisTypes.unknown()) {
+            /* Don't duplicate the logic in js_CreateThis, just mark 'this' as unknown. */
+            js::types::InferSpew(js::types::ISpewDynamic, "UnknownThis: #%u", script->id);
+            compartment->types.addDynamicType(this, &script->thisTypes, js::types::TYPE_UNKNOWN);
+        }
+    } else {
+        js::types::jstype type = js::types::GetValueType(this, args.thisv());
         if (!script->thisTypes.hasType(type)) {
             js::types::InferSpew(js::types::ISpewDynamic, "AddThis: #%u: %s",
                                  script->id, js::types::TypeString(type));
             compartment->types.addDynamicType(this, &script->thisTypes, type);
         }
     }
 
     unsigned arg = 0;
@@ -942,35 +942,35 @@ HashSetLookup(U **values, unsigned count
 struct TypeObjectKey {
     static uint32 keyBits(TypeObject *obj) { return (uint32) obj; }
     static TypeObject *getKey(TypeObject *obj) { return obj; }
 };
 
 inline bool
 TypeSet::hasType(jstype type)
 {
-    if (typeFlags & TYPE_FLAG_UNKNOWN)
+    if (unknown())
         return true;
 
     if (TypeIsPrimitive(type)) {
         return ((1 << type) & typeFlags) != 0;
     } else {
         return HashSetLookup<TypeObject*,TypeObject,TypeObjectKey>
             (objectSet, objectCount, (TypeObject *) type) != NULL;
     }
 }
 
 inline void
 TypeSet::addType(JSContext *cx, jstype type)
 {
     JS_ASSERT(type);
-    JS_ASSERT_IF(typeFlags & TYPE_FLAG_UNKNOWN, typeFlags == TYPE_FLAG_UNKNOWN);
+    JS_ASSERT_IF(unknown(), typeFlags == TYPE_FLAG_UNKNOWN);
     InferSpew(ISpewOps, "addType: T%u %s", id(), TypeString(type));
 
-    if (typeFlags & TYPE_FLAG_UNKNOWN)
+    if (unknown())
         return;
 
     if (type == TYPE_UNKNOWN) {
         typeFlags = TYPE_FLAG_UNKNOWN;
     } else if (TypeIsPrimitive(type)) {
         TypeFlags flag = 1 << type;
         if (typeFlags & flag)
             return;
@@ -1163,20 +1163,30 @@ inline TypeObject::TypeObject(JSArenaPoo
 #endif
 
 #ifdef JS_TYPE_INFERENCE
     InferSpew(ISpewOps, "newObject: %s", this->name());
 #endif
 
     if (proto) {
         TypeObject *prototype = proto->getType();
-        if (prototype->unknownProperties)
+        if (prototype->unknownProperties) {
             unknownProperties = true;
-        else if (proto->isArray())
+        } else if (proto->isArray()) {
+            /*
+             * Note: this check is insufficient for determining whether new objects
+             * are dense arrays, as they may not themselves be arrays but simply
+             * have an array or Array.prototype as their prototype. We can't use
+             * a clasp here as type does not determine the clasp of an object, so we
+             * intercept at the places where a non-Array can have an Array as its
+             * prototype --- scripted 'new', reassignments to __proto__, Object.create
+             * and through the API.
+             */
             isDenseArray = isPackedArray = true;
+        }
         instanceNext = prototype->instanceList;
         prototype->instanceList = this;
     }
 }
 
 inline TypeFunction::TypeFunction(JSArenaPool *pool, jsid name, JSObject *proto)
     : TypeObject(pool, name, proto), handler(NULL), script(NULL),
       returnTypes(pool), isGeneric(false)
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2509,16 +2509,19 @@ obj_create(JSContext *cx, uintN argc, Va
      * scoping (i.e., using the caller's global).
      */
     JSObject *obj = NewNonFunction<WithProto::Given>(cx, &js_ObjectClass, v.toObjectOrNull(),
                                                         vp->toObject().getGlobal());
     if (!obj)
         return JS_FALSE;
     vp->setObject(*obj); /* Root and prepare for eventual return. */
 
+    /* Don't track types or array-ness for objects created here. */
+    cx->markTypeObjectUnknownProperties(obj->getType());
+
     /* 15.2.3.5 step 4. */
     if (argc > 1 && !vp[3].isUndefined()) {
         if (vp[3].isPrimitive()) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_NONNULL_OBJECT);
             return JS_FALSE;
         }
 
         JSObject *props = &vp[3].toObject();
@@ -2837,38 +2840,50 @@ js_CreateThis(JSContext *cx, JSObject *c
     Value protov;
     if (!callee->getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom), &protov))
         return NULL;
 
     JSObject *proto = protov.isObjectOrNull() ? protov.toObjectOrNull() : NULL;
     JSObject *parent = callee->getParent();
     gc::FinalizeKind kind = NewObjectGCKind(cx, newclasp);
     JSObject *obj = NewObject<WithProto::Class>(cx, newclasp, proto, parent, kind);
-    if (obj)
+    if (obj) {
         obj->syncSpecialEquality();
+        cx->markTypeArrayNotPacked(obj->getType(), true, true);
+    }
     return obj;
 }
 
 JSObject *
 js_CreateThisForFunctionWithProto(JSContext *cx, JSObject *callee, JSObject *proto)
 {
+    /* Caller must ensure that proto's new type is not marked as an array. */
     gc::FinalizeKind kind = NewObjectGCKind(cx, &js_ObjectClass);
     return NewNonFunction<WithProto::Class>(cx, &js_ObjectClass, proto, callee->getParent(), kind);
 }
 
 JSObject *
 js_CreateThisForFunction(JSContext *cx, JSObject *callee)
 {
     Value protov;
     if (!callee->getProperty(cx,
                              ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom),
                              &protov)) {
         return NULL;
     }
-    JSObject *proto = protov.isObject() ? &protov.toObject() : NULL;
+    JSObject *proto;
+    if (protov.isObject()) {
+        proto = &protov.toObject();
+        TypeObject *type = proto->getNewType(cx);
+        if (!type)
+            return NULL;
+        cx->markTypeArrayNotPacked(type, true, true);
+    } else {
+        proto = NULL;
+    }
     return js_CreateThisForFunctionWithProto(cx, callee, proto);
 }
 
 #ifdef JS_TRACER
 
 static JS_ALWAYS_INLINE JSObject*
 NewObjectWithClassProto(JSContext *cx, Class *clasp, JSObject *proto,
                         /*gc::FinalizeKind*/ unsigned _kind)
@@ -2943,16 +2958,20 @@ js_CreateThisFromTrace(JSContext *cx, Cl
     const Shape *shape = ctor->nativeLookup(classPrototypeId);
     Value pval = shape ? ctor->getSlot(shape->slot) : MagicValue(JS_GENERIC_MAGIC);
 
     JSObject *parent = ctor->getParent();
     JSObject *proto;
     if (pval.isObject()) {
         /* An object in ctor.prototype, let's use it as the new instance's proto. */
         proto = &pval.toObject();
+        TypeObject *type = proto->getNewType(cx);
+        if (!type)
+            return NULL;
+        cx->markTypeArrayNotPacked(type, true, true);
     } else {
         /* A hole or a primitive: either way, we need to get Object.prototype. */
         JSObject *objProto;
         if (!js_GetClassPrototype(cx, parent, JSProto_Object, &objProto))
             return NULL;
 
         if (pval.isMagic(JS_GENERIC_MAGIC)) {
             /*
@@ -4371,16 +4390,17 @@ js_ConstructObject(JSContext *cx, Class 
             proto = rval.toObjectOrNull();
     }
 
     JSObject *obj = NewObject<WithProto::Class>(cx, clasp, proto, parent);
     if (!obj)
         return NULL;
 
     obj->syncSpecialEquality();
+    cx->markTypeObjectUnknownProperties(obj->getType());
 
     Value rval;
     if (!InvokeConstructorWithGivenThis(cx, obj, cval, argc, argv, &rval))
         return NULL;
 
     if (rval.isPrimitive())
         return obj;
 
--- a/js/src/jsxml.cpp
+++ b/js/src/jsxml.cpp
@@ -7114,17 +7114,16 @@ js_GetXMLObject(JSContext *cx, JSXML *xm
         return NULL;
     xml->object = obj;
     return obj;
 }
 
 JSObject *
 js_InitNamespaceClass(JSContext *cx, JSObject *obj)
 {
-    /* :FIXME: handler is broken here and below when called with other object as 'this'. */
     return js_InitClass(cx, obj, NULL, &js_NamespaceClass, Namespace, 2, JS_TypeHandlerDynamic,
                         namespace_props, namespace_methods, NULL, NULL);
 }
 
 JSObject *
 js_InitQNameClass(JSContext *cx, JSObject *obj)
 {
     return js_InitClass(cx, obj, NULL, &js_QNameClass, QName, 2, JS_TypeHandlerDynamic,
@@ -7165,16 +7164,22 @@ js_InitXMLClass(JSContext *cx, JSObject 
 
     /* Define the XML class constructor and prototype. */
     proto = js_InitClass(cx, obj, NULL, &js_XMLClass, XML, 1, JS_TypeHandlerDynamic,
                          NULL, xml_methods,
                          xml_static_props, xml_static_methods);
     if (!proto)
         return NULL;
 
+    /* Properties of XML objects are not modeled by type inference. */
+    TypeObject *type = proto->getNewType(cx);
+    if (!type)
+        return NULL;
+    type->markUnknown(cx);
+
     xml = js_NewXML(cx, JSXML_CLASS_TEXT);
     if (!xml)
         return NULL;
     proto->setPrivate(xml);
     xml->object = proto;
     METER(xml_stats.xmlobj);
 
     /*
--- a/js/src/methodjit/PolyIC.cpp
+++ b/js/src/methodjit/PolyIC.cpp
@@ -1696,17 +1696,24 @@ ic::GetProp(VMFrame &f, ic::PICInfo *pic
             THROW();
         }
     }
 
     Value v;
     if (!obj->getProperty(f.cx, ATOM_TO_JSID(atom), &v))
         THROW();
 
-    if (v.isUndefined())
+    /*
+     * Ignore undefined reads for the 'prototype' property in constructors,
+     * which will be at the start of the script and are never holes due to fun_resolve.
+     * Any undefined value was explicitly stored here, and is known by inference.
+     * :FIXME: looking under the usePropCache abstraction, which is only unset for
+     * reads of the prototype.
+     */
+    if (v.isUndefined() && pic->usePropCache)
         f.script()->typeMonitorUndefined(f.cx, f.regs.pc, 0);
 
     f.regs.sp[-1] = v;
 }
 
 template <JSBool strict>
 static void JS_FASTCALL
 DisabledSetPropIC(VMFrame &f, ic::PICInfo *pic)
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -2094,18 +2094,17 @@ stubs::GetPropNoCache(VMFrame &f, JSAtom
     Value *vp = &f.regs.sp[-1];
     JSObject *obj = ValueToObject(cx, vp);
     if (!obj)
         THROW();
 
     if (!obj->getProperty(cx, ATOM_TO_JSID(atom), vp))
         THROW();
 
-    if (vp->isUndefined())
-        f.script()->typeMonitorUndefined(cx, f.regs.pc, 0);
+    /* Don't check for undefined, this is only used for 'prototype'. See ic::GetProp. */
 }
 
 void JS_FASTCALL
 stubs::CallProp(VMFrame &f, JSAtom *origAtom)
 {
     JSContext *cx = f.cx;
     JSFrameRegs &regs = f.regs;