[INFER] Overhaul inference handling of new object computation, bug 619433.
authorBrian Hackett <bhackett1024@gmail.com>
Thu, 10 Mar 2011 12:01:11 -0800
changeset 74752 af764018d6f7e354fb7bedc68cdc79909cdb00e7
parent 74751 9cbb9f9e8ac9cae4823b75e03ed93e97682c8baf
child 74753 154ac7e67f8ba21472334a2e6b03f803023987b3
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
bugs619433
milestone2.0b13pre
[INFER] Overhaul inference handling of new object computation, bug 619433.
js/src/jscntxt.h
js/src/jsfun.cpp
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
js/src/jsobj.cpp
js/src/jsscript.h
js/src/jsxml.cpp
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -2136,17 +2136,18 @@ public:
 
     inline bool typeInferenceEnabled();
 
     /* Make a type function or object with the specified name. */
     js::types::TypeFunction *newTypeFunction(const char *name, JSObject *proto);
     js::types::TypeObject   *newTypeObject(const char *name, JSObject *proto);
 
     /* Make a type object whose name is that of base followed by postfix. */
-    js::types::TypeObject *newTypeObject(const char *base, const char *postfix, JSObject *proto);
+    js::types::TypeObject *newTypeObject(const char *base, const char *postfix,
+                                         JSObject *proto, bool isFunction = false);
 
     /*
      * Get the default 'new' object for a given standard class, per the currently
      * active global.
      */
     inline js::types::TypeObject *getTypeNewObject(JSProtoKey key);
 
     /* Get a type object for the immediate allocation site in this context. */
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -2759,16 +2759,18 @@ js_InitFunctionClass(JSContext *cx, JSOb
         return NULL;
     script->noScriptRval = true;
     script->code[0] = JSOP_STOP;
     script->code[1] = SRC_NULL;
 #ifdef CHECK_SCRIPT_OWNER
     script->owner = NULL;
 #endif
     fun->u.i.script = script;
+    fun->getType()->asFunction()->script = script;
+    script->fun = fun;
     js_CallNewScriptHook(cx, script, fun);
 
     if (obj->isGlobal()) {
         /* ES5 13.2.3: Construct the unique [[ThrowTypeError]] function object. */
         JSObject *throwTypeError =
             js_NewFunction(cx, NULL, reinterpret_cast<Native>(ThrowTypeError), 0,
                            0, obj, NULL, JS_TypeHandlerVoid, "ThrowTypeError");
         if (!throwTypeError)
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -969,18 +969,19 @@ TypeConstraintNewObject::newType(JSConte
         } else {
             TypeSet *newTypes = object->getProperty(cx, JSID_EMPTY, true);
             if (!newTypes)
                 return;
             newTypes->addMonitorRead(cx, script, target);
         }
     } else if (!fun->script) {
         /*
-         * This constraint should only be used for native constructors with
-         * immutable non-primitive prototypes. Disregard primitives here.
+         * This constraint should only be used for scripted functions and for
+         * native constructors with immutable non-primitive prototypes.
+         * Disregard primitives here.
          */
     } else if (!fun->script->compileAndGo) {
         target->addType(cx, TYPE_UNKNOWN);
     } else {
         TypeObject *object = fun->script->getTypeNewObject(cx, JSProto_Object);
         if (!object) {
             cx->compartment->types.setPendingNukeTypes(cx);
             return;
@@ -1078,25 +1079,17 @@ TypeConstraintCall::newType(JSContext *c
     /* Add void type for any formals in the callee not supplied at the call site. */
     for (unsigned i = callsite->argumentCount; i < nargs; i++) {
         TypeSet *types = callee->argTypes(i);
         types->addType(cx, TYPE_UNDEFINED);
     }
 
     /* 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);
-            if (!prototypeTypes)
-                return;
-            prototypeTypes->addNewObject(cx, script, function, callee->thisTypes());
-        }
+        callee->typeSetNewCalled(cx);
 
         /*
          * If the script does not return a value then the pushed value is the new
          * object (typical case).
          */
         if (callsite->returnTypes) {
             callee->thisTypes()->addSubset(cx, script, callsite->returnTypes);
             callee->returnTypes()->addFilterPrimitives(cx, script,
@@ -1682,35 +1675,24 @@ TypeCompartment::growPendingArray(JSCont
 
 bool
 TypeCompartment::dynamicCall(JSContext *cx, JSObject *callee,
                              const js::CallArgs &args, bool constructing)
 {
     unsigned nargs = callee->getFunctionPrivate()->nargs;
     JSScript *script = callee->getFunctionPrivate()->script();
 
-    jstype type;
     if (constructing) {
-        Value protov;
-        jsid id = ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom);
-        if (!callee->getProperty(cx, id, &protov))
+        script->typeSetNewCalled(cx);
+    } else {
+        jstype type = GetValueType(cx, args.thisv());
+        if (!script->typeSetThis(cx, type))
             return false;
-        TypeObject *otype = protov.isObject()
-            ? protov.toObject().getNewType(cx)
-            : cx->getTypeNewObject(JSProto_Object);
-        if (!otype)
-            return false;
-        type = (jstype) otype;
-    } else {
-        type = GetValueType(cx, args.thisv());
     }
 
-    if (!script->typeSetThis(cx, type))
-        return false;
-
     /*
      * Add constraints going up to the minimum of the actual and formal count.
      * If there are more actuals than formals the later values can only be
      * accessed through the arguments object, which is monitored.
      */
     unsigned arg = 0;
     for (; arg < args.argc() && arg < nargs; arg++) {
         if (!script->typeSetArgument(cx, arg, args[arg]))
@@ -3301,16 +3283,19 @@ AnalyzeScriptTypes(JSContext *cx, JSScri
     script->analyzed = true;
 #ifdef DEBUG
     types->script = script;
 #endif
 
     cursor += sizeof(TypeScript);
     types->pushedArray = (TypeSet **) cursor;
 
+    if (script->calledWithNew)
+        AnalyzeScriptNew(cx, script);
+
     unsigned offset = 0;
     while (offset < script->length) {
         analyze::Bytecode *code = analysis.maybeCode(offset);
 
         jsbytecode *pc = script->code + offset;
         analyze::UntrapOpcode untrap(cx, script, pc);
 
         if (code && !AnalyzeBytecode(cx, state, script, offset)) {
@@ -3330,21 +3315,33 @@ AnalyzeScriptTypes(JSContext *cx, JSScri
     while (result) {
         TypeSet *pushed = script->types->pushed(result->offset);
         pushed->addType(cx, result->type);
         result = result->next;
     }
 }
 
 void
-DestroyScriptTypes(JSContext *cx, JSScript *script)
+AnalyzeScriptNew(JSContext *cx, JSScript *script)
 {
-    JS_ASSERT(script->types);
-    cx->free(script->types);
-    script->types = NULL;
+    JS_ASSERT(script->calledWithNew && script->fun);
+
+    /*
+     * Compute the 'this' type when called with 'new'. We do not distinguish regular
+     * from 'new' calls to the function.
+     */
+    TypeFunction *funType = script->fun->getType()->asFunction();
+    if (funType->unknownProperties) {
+        script->thisTypes()->addType(cx, TYPE_UNKNOWN);
+    } else {
+        TypeSet *prototypeTypes = funType->getProperty(cx, id_prototype(cx), false);
+        if (!prototypeTypes)
+            return;
+        prototypeTypes->addNewObject(cx, script, funType, script->thisTypes());
+    }
 }
 
 /////////////////////////////////////////////////////////////////////
 // Printing
 /////////////////////////////////////////////////////////////////////
 
 #ifdef DEBUG
 
@@ -3571,25 +3568,25 @@ JSContext::newTypeFunction(const char *n
 
 js::types::TypeObject *
 JSContext::newTypeObject(const char *name, JSObject *proto)
 {
     return compartment->types.newTypeObject(this, NULL, name, false, proto);
 }
 
 js::types::TypeObject *
-JSContext::newTypeObject(const char *base, const char *postfix, JSObject *proto)
+JSContext::newTypeObject(const char *base, const char *postfix, JSObject *proto, bool isFunction)
 {
     char *name = NULL;
 #ifdef DEBUG
     unsigned len = strlen(base) + strlen(postfix) + 5;
     name = (char *)alloca(len);
     JS_snprintf(name, len, "%s:%s", base, postfix);
 #endif
-    return compartment->types.newTypeObject(this, NULL, name, false, proto);
+    return compartment->types.newTypeObject(this, NULL, name, isFunction, proto);
 }
 
 /////////////////////////////////////////////////////////////////////
 // JSScript
 /////////////////////////////////////////////////////////////////////
 
 /*
  * Returns true if we don't expect to compute the correct types for some value
@@ -4112,11 +4109,13 @@ JSScript::condenseTypes(JSContext *cx)
     }
 }
 
 void
 JSScript::sweepTypes(JSContext *cx)
 {
     SweepTypeObjectList(cx, typeObjects);
 
-    if (types && !compartment->types.inferenceDepth)
-        js::types::DestroyScriptTypes(cx, this);
+    if (types && !compartment->types.inferenceDepth) {
+        cx->free(types);
+        types = NULL;
+    }
 }
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -565,18 +565,18 @@ struct TypeScript
     inline TypeSet *pushed(uint32 offset, uint32 index);
 
     inline void addType(JSContext *cx, uint32 offset, uint32 index, jstype type);
 };
 
 /* Analyzes all types in script, constructing its TypeScript. */
 void AnalyzeScriptTypes(JSContext *cx, JSScript *script);
 
-/* Destroy the TypeScript associated with a script. */
-void DestroyScriptTypes(JSContext *cx, JSScript *script);
+/* Analyze the effect of invoking 'new' on script. */
+void AnalyzeScriptNew(JSContext *cx, JSScript *script);
 
 /* Type information for a compartment. */
 struct TypeCompartment
 {
     /* List of objects not associated with a script. */
     TypeObject *objects;
 
     /* Whether type inference is enabled in this compartment. */
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -526,16 +526,40 @@ JSScript::typeSetThis(JSContext *cx, js:
 
         return cx->compartment->types.checkPendingRecompiles(cx);
     }
 
     return true;
 }
 
 inline bool
+JSScript::typeSetNewCalled(JSContext *cx)
+{
+    if (!cx->typeInferenceEnabled() || calledWithNew)
+        return true;
+    calledWithNew = true;
+
+    /*
+     * Determining the 'this' type used when the script is invoked with 'new'
+     * happens during the script's prologue, so we don't try to pick it up from
+     * dynamic calls. Instead, generate constraints modeling the construction
+     * of 'this' when the script is analyzed or reanalyzed after an invoke with 'new',
+     * and if 'new' is first invoked after the script has already been analyzed.
+     */
+    if (analyzed) {
+        /* Regenerate types for the function. */
+        js::types::AutoEnterTypeInference enter(cx);
+        js::types::AnalyzeScriptNew(cx, this);
+        if (!cx->compartment->types.checkPendingRecompiles(cx))
+            return false;
+    }
+    return true;
+}
+
+inline bool
 JSScript::typeSetLocal(JSContext *cx, unsigned local, const js::Value &value)
 {
     if (!cx->typeInferenceEnabled())
         return true;
     if (!ensureVarTypes(cx))
         return false;
     js::types::jstype type = js::types::GetValueType(cx, value);
     if (!localTypes(local)->hasType(type)) {
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3935,17 +3935,18 @@ DefineConstructorAndPrototype(JSContext 
      * be &js_FunctionClass (we could break compatibility easily). But fixing
      * (3) is not enough without addressing the bootstrapping dependency on (1)
      * and (2).
      */
     JSObject *proto = NewObject<WithProto::Class>(cx, clasp, protoProto, obj);
     if (!proto)
         return NULL;
 
-    TypeObject *protoType = cx->newTypeObject(clasp->name, "prototype", proto->getProto());
+    TypeObject *protoType = cx->newTypeObject(clasp->name, "prototype", proto->getProto(),
+                                              clasp == &js_FunctionClass);
     if (!protoType)
         return NULL;
     proto->setType(protoType);
 
     if (clasp == &js_ArrayClass && !proto->makeDenseArraySlow(cx))
         return NULL;
 
     TypeObject *type = proto->getNewType(cx);
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -401,16 +401,17 @@ struct JSScript {
     bool            usesEval:1;       /* script uses eval() */
     bool            usesArguments:1;  /* script uses arguments */
     bool            warnedAboutTwoArgumentEval:1; /* have warned about use of
                                                      obsolete eval(s, o) in
                                                      this script */
     bool            hasSingletons:1;  /* script has singleton objects */
     bool            isCachedEval:1;   /* script came from eval() */
     bool            isUncachedEval:1; /* script came from EvaluateScript */
+    bool            calledWithNew:1;  /* script has been called using 'new' */
     bool            analyzed:1;       /* script has been analyzed by type inference */
 #ifdef JS_METHODJIT
     bool            debugMode:1;      /* script was compiled in debug mode */
     bool            singleStepMode:1; /* compile script in single-step mode */
 #endif
 
     jsbytecode      *main;      /* main entry point, after predef'ing prolog */
     JSAtomMap       atomMap;    /* maps immediate index to literal struct */
@@ -517,16 +518,17 @@ struct JSScript {
     inline bool typeMonitorResult(JSContext *cx, const jsbytecode *pc, js::types::jstype type);
     inline bool typeMonitorResult(JSContext *cx, const jsbytecode *pc, const js::Value &val);
     inline bool typeMonitorUndefined(JSContext *cx, const jsbytecode *pc);
     inline bool typeMonitorOverflow(JSContext *cx, const jsbytecode *pc);
     inline bool typeMonitorUnknown(JSContext *cx, const jsbytecode *pc);
 
     /* Add a type for a variable in this script. */
     inline bool typeSetThis(JSContext *cx, js::types::jstype type);
+    inline bool typeSetNewCalled(JSContext *cx);
     inline bool typeSetLocal(JSContext *cx, unsigned local, const js::Value &value);
     inline bool typeSetArgument(JSContext *cx, unsigned arg, js::types::jstype type);
     inline bool typeSetArgument(JSContext *cx, unsigned arg, const js::Value &value);
     inline bool typeSetUpvar(JSContext *cx, unsigned upvar, const js::Value &value);
 
     /*
      * Associates this script with a specific function, constructing a new type
      * object for the function.
--- a/js/src/jsxml.cpp
+++ b/js/src/jsxml.cpp
@@ -7109,16 +7109,18 @@ js_InitNamespaceClass(JSContext *cx, JSO
                         namespace_props, namespace_methods, NULL, NULL);
 }
 
 JSObject *
 js_InitQNameClass(JSContext *cx, JSObject *obj)
 {
     JSObject *proto = js_InitClass(cx, obj, NULL, &js_QNameClass, QName, 2, JS_TypeHandlerDynamic,
                                    qname_props, qname_methods, NULL, NULL);
+    if (!proto)
+        return NULL;
 
     /* Properties of QName objects are not modeled by type inference. */
     TypeObject *type = proto->getNewType(cx);
     if (!type ||
         !cx->markTypeObjectUnknownProperties(type) ||
         !cx->markTypeObjectUnknownProperties(proto->getType())) {
         return NULL;
     }
@@ -7300,16 +7302,20 @@ js_SetDefaultXMLNamespace(JSContext *cx,
     argv[0].setString(cx->runtime->emptyString);
     argv[1] = v;
     JSObject *ns = js_ConstructObject(cx, &js_NamespaceClass, NULL, NULL, 2, argv);
     if (!ns)
         return JS_FALSE;
 
     JSStackFrame *fp = js_GetTopStackFrame(cx);
     JSObject &varobj = fp->varobj(cx);
+
+    if (!cx->addTypePropertyId(varobj.getType(), JS_DEFAULT_XML_NAMESPACE_ID, types::TYPE_UNKNOWN))
+        return JS_FALSE;
+
     if (!varobj.defineProperty(cx, JS_DEFAULT_XML_NAMESPACE_ID, ObjectValue(*ns),
                                PropertyStub, StrictPropertyStub, JSPROP_PERMANENT)) {
         return JS_FALSE;
     }
     return JS_TRUE;
 }
 
 JSBool