[INFER] Handle reparenting of non-compileAndGo scripts, bug 620599.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 22 Dec 2010 14:46:42 -0800
changeset 74685 2d8ab0c4425517c16d66c70b892a768c9840f05f
parent 74684 955c4fbfbd09cf1ab6458cbe24cd411c3f2ec5f3
child 74686 6ae854b6490f5c99555f1d9d811844b8949da8fe
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
bugs620599
milestone2.0b8pre
[INFER] Handle reparenting of non-compileAndGo scripts, bug 620599.
js/src/jsanalyze.cpp
js/src/jsanalyze.h
js/src/jsapi.cpp
js/src/jsinfer.cpp
js/src/jsinferinlines.h
js/src/jsscript.h
js/src/tests/js1_5/extensions/regress-300079.js
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -464,36 +464,16 @@ BytecodeNoFallThrough(JSOp op)
       case JSOP_GOSUBX:
         // these fall through indirectly, after executing a 'finally'.
         return false;
       default:
         return false;
     }
 }
 
-/* Untrap a single PC, and retrap it at scope exit. */
-struct UntrapOpcode
-{
-    jsbytecode *pc;
-    bool trap;
-
-    UntrapOpcode(JSContext *cx, JSScript *script, jsbytecode *pc)
-        : pc(pc), trap(JSOp(*pc) == JSOP_TRAP)
-    {
-        if (trap)
-            *pc = JS_GetTrapOpcode(cx, script, pc);
-    }
-
-    ~UntrapOpcode()
-    {
-        if (trap)
-            *pc = JSOP_TRAP;
-    }
-};
-
 #ifdef JS_TYPE_INFERENCE
 
 /*
  * Information about a currently active static initializer.  We keep the stack
  * of initializers around during analysis so we can reuse objects when
  * processing arrays of arrays or arrays of objects.
  */
 struct InitializerInfo
--- a/js/src/jsanalyze.h
+++ b/js/src/jsanalyze.h
@@ -357,16 +357,18 @@ class Script
      * Get the non-eval script which this one is nested in, returning this script
      * if it was not produced as the result of an eval.
      */
     inline Script *evalParent();
 
     /* Bytecode where this script is nested. */
     inline Bytecode *parentCode();
 
+    void nukeUpvarTypes(JSContext *cx);
+
     /* Gather statistics off this script and print it if necessary. */
     void finish(JSContext *cx);
 
     /* Helpers */
 
     /* Inference state destroyed after the initial pass through the function. */
 
     struct AnalyzeStateStack {
@@ -525,16 +527,36 @@ GetDefCount(JSScript *script, unsigned o
         return 1;
       case JSOP_FILTER:
         return 2;
       default:
         return js_CodeSpec[*pc].ndefs;
     }
 }
 
+/* Untrap a single PC, and retrap it at scope exit. */
+struct UntrapOpcode
+{
+    jsbytecode *pc;
+    bool trap;
+
+    UntrapOpcode(JSContext *cx, JSScript *script, jsbytecode *pc)
+        : pc(pc), trap(JSOp(*pc) == JSOP_TRAP)
+    {
+        if (trap)
+            *pc = JS_GetTrapOpcode(cx, script, pc);
+    }
+
+    ~UntrapOpcode()
+    {
+        if (trap)
+            *pc = JSOP_TRAP;
+    }
+};
+
 /*
  * Lifetime analysis. The goal of this analysis is to make a single backwards pass
  * over a script to approximate the regions where each variable is live, without
  * doing a full fixpointing live-variables pass. This is based on the algorithm
  * described in:
  *
  * "Quality and Speed in Linear-scan Register Allocation"
  * Traub et. al.
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4282,19 +4282,31 @@ JS_CloneFunctionObject(JSContext *cx, JS
          * idea, but we changed incompatibly to teach any abusers a lesson!).
          */
         Value v = ObjectValue(*funobj);
         js_ReportIsNotFunction(cx, &v, 0);
         return NULL;
     }
 
     JSFunction *fun = GET_FUNCTION_PRIVATE(cx, funobj);
+    if (!fun->isInterpreted())
+        return CloneFunctionObject(cx, fun, parent);
+
+    if (fun->script()->compileAndGo) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                             JSMSG_BAD_CLONE_FUNOBJ_SCOPE);
+        return NULL;
+    }
+
     if (!FUN_FLAT_CLOSURE(fun))
         return CloneFunctionObject(cx, fun, parent);
 
+    /* Throw away all inferred types about upvars in this script and its children. */
+    fun->script()->nukeUpvarTypes(cx);
+
     /*
      * A flat closure carries its own environment, so why clone it? In case
      * someone wants to mutate its fixed slots or add ad-hoc properties. API
      * compatibility suggests we not return funobj and let callers mutate the
      * returned object at will.
      *
      * But it's worse than that: API compatibility according to the test for
      * bug 300079 requires we get "upvars" from parent and its ancestors! So
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -2779,20 +2779,16 @@ Script::analyzeTypes(JSContext *cx, Byte
         break;
 
       case JSOP_LAMBDA:
       case JSOP_LAMBDA_FC:
       case JSOP_DEFFUN:
       case JSOP_DEFFUN_FC:
       case JSOP_DEFLOCALFUN:
       case JSOP_DEFLOCALFUN_FC: {
-        unsigned funOffset = 0;
-        if (op == JSOP_DEFLOCALFUN || op == JSOP_DEFLOCALFUN_FC)
-            funOffset = SLOTNO_LEN;
-
         unsigned off = (op == JSOP_DEFLOCALFUN || op == JSOP_DEFLOCALFUN_FC) ? SLOTNO_LEN : 0;
         JSObject *obj = GetScriptObject(cx, script, pc, off);
         TypeFunction *function = obj->getType()->asFunction();
 
         /* Remember where this script was defined. */
         function->script->analysis->parent = script;
         function->script->analysis->parentpc = pc;
 
@@ -3223,16 +3219,78 @@ Script::analyzeTypes(JSContext *cx, Byte
                 state.popped(0).isZero = true;
         }
         break;
 
       default:;
     }
 }
 
+void
+Script::nukeUpvarTypes(JSContext *cx)
+{
+    JS_ASSERT(parent && !getScript()->compileAndGo);
+
+    if (!hasAnalyzed())
+        analyze(cx);
+
+    parent = NULL;
+    parentpc = NULL;
+
+    unsigned offset = 0;
+    while (offset < script->length) {
+        Bytecode *code = maybeCode(offset);
+
+        jsbytecode *pc = script->code + offset;
+        analyze::UntrapOpcode untrap(cx, script, pc);
+
+        offset += GetBytecodeLength(pc);
+
+        if (!code)
+            continue;
+
+        JSOp op = JSOp(*pc);
+        switch (op) {
+          case JSOP_GETUPVAR:
+          case JSOP_CALLUPVAR:
+          case JSOP_GETFCSLOT:
+          case JSOP_CALLFCSLOT:
+          case JSOP_GETXPROP:
+          case JSOP_NAME:
+          case JSOP_CALLNAME:
+            code->setFixed(cx, 0, TYPE_UNKNOWN);
+            break;
+
+          case JSOP_SETNAME:
+          case JSOP_FORNAME:
+          case JSOP_INCNAME:
+          case JSOP_DECNAME:
+          case JSOP_NAMEINC:
+          case JSOP_NAMEDEC:
+            cx->compartment->types.monitorBytecode(cx, code);
+            break;
+
+          case JSOP_LAMBDA:
+          case JSOP_LAMBDA_FC:
+          case JSOP_DEFFUN:
+          case JSOP_DEFFUN_FC:
+          case JSOP_DEFLOCALFUN:
+          case JSOP_DEFLOCALFUN_FC: {
+            unsigned off = (op == JSOP_DEFLOCALFUN || op == JSOP_DEFLOCALFUN_FC) ? SLOTNO_LEN : 0;
+            JSObject *obj = GetScriptObject(cx, script, pc, off);
+            TypeFunction *function = obj->getType()->asFunction();
+            function->script->nukeUpvarTypes(cx);
+            break;
+          }
+
+          default:;
+        }
+    }
+}
+
 /////////////////////////////////////////////////////////////////////
 // Printing
 /////////////////////////////////////////////////////////////////////
 
 #ifdef DEBUG
 
 void
 Bytecode::print(JSContext *cx)
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -504,16 +504,25 @@ inline void
 JSScript::setTypeNesting(JSScript *parent, const jsbytecode *pc)
 {
 #ifdef JS_TYPE_INFERENCE
     analysis->parent = parent;
     analysis->parentpc = pc;
 #endif
 }
 
+inline void
+JSScript::nukeUpvarTypes(JSContext *cx)
+{
+#ifdef JS_TYPE_INFERENCE
+    if (analysis->parent)
+        analysis->nukeUpvarTypes(cx);
+#endif
+}
+
 inline js::types::TypeObject *
 JSScript::getTypeInitObject(JSContext *cx, const jsbytecode *pc, bool isArray)
 {
 #ifdef JS_TYPE_INFERENCE
     if (!compileAndGo || analysis->failed())
         return cx->getTypeNewObject(isArray ? JSProto_Array : JSProto_Object);
     return analysis->getCode(pc).getInitObject(cx, isArray);
 #else
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -292,16 +292,22 @@ struct JSScript {
     js::analyze::Script *makeAnalysis(JSContext *cx);
 
     /* Check that correct types were inferred for the values popped by this bytecode. */
     void typeCheckBytecode(JSContext *cx, const jsbytecode *pc, const js::Value *sp);
 
     /* Mark this script as having been created at the specified script/pc. */
     inline void setTypeNesting(JSScript *parent, const jsbytecode *pc);
 
+    /*
+     * Throw away all upvar information in this script and its children, and detach from
+     * any parent it is nested in. For reparenting functions to arbitrary objects.
+     */
+    inline void nukeUpvarTypes(JSContext *cx);
+
     /* Get a type object for an allocation site in this script. */
     inline js::types::TypeObject *
     getTypeInitObject(JSContext *cx, const jsbytecode *pc, bool isArray);
 
     /* Monitor a bytecode pushing an unexpected value. */
     inline void typeMonitorResult(JSContext *cx, const jsbytecode *pc, unsigned index,
                                   js::types::jstype type);
     inline void typeMonitorResult(JSContext *cx, const jsbytecode *pc, unsigned index,
--- a/js/src/tests/js1_5/extensions/regress-300079.js
+++ b/js/src/tests/js1_5/extensions/regress-300079.js
@@ -54,17 +54,17 @@ function test()
 
   if (typeof clone == 'undefined') {
     expect = 'SKIPPED';
     actual = 'SKIPPED';
   }
   else {
     expect = 'PASSED';
 
-    f = eval("(function (a) {return (function () { return a * a;}); })()");
+    f = Function("a", "return (function () { return a * a;});")();
     g = clone(f, {a: 3});
     f = null;
     gc();
     try {
       a_squared = g(2);
       if (a_squared != 9)
         throw "Unexpected return from g: a_squared == " + a_squared;
       actual = "PASSED";