Bug 514570 - 2 - Push |undefined| rather than |null| when calling functions without a specified |this| value, per ES5. r=jorendorff
authorJeff Walden <jwalden@mit.edu>
Tue, 12 Oct 2010 11:50:03 -0700
changeset 55712 08552482670daab4f3a3bcc45cd31950c93824df
parent 55711 66710af05aa1d25135e3a610cfb7f4bf5e9010f8
child 55713 38cbd4e02afc0c69ac3ec8002446ac4ce3b1f2b8
push id16269
push userjst@mozilla.com
push dateThu, 14 Oct 2010 01:40:35 +0000
treeherdermozilla-central@29c228a4d7eb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs514570
milestone2.0b8pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 514570 - 2 - Push |undefined| rather than |null| when calling functions without a specified |this| value, per ES5. r=jorendorff
js/src/jsarray.cpp
js/src/jsemit.cpp
js/src/jsfun.cpp
js/src/jsinterp.cpp
js/src/jsobj.cpp
js/src/jsopcode.cpp
js/src/jsstr.cpp
js/src/jstracer.cpp
js/src/methodjit/Compiler.cpp
js/src/methodjit/FrameState-inl.h
js/src/methodjit/FrameState.h
js/src/methodjit/NunboxAssembler.h
js/src/methodjit/PunboxAssembler.h
js/src/methodjit/StubCalls.cpp
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -2811,22 +2811,27 @@ array_extra(JSContext *cx, ArrayExtraMod
         break;
     }
 
     if (length == 0)
         return JS_TRUE;
 
     Value thisv;
     if (argc > 1 && !REDUCE_MODE(mode)) {
-        JSObject *thisp;
-        if (!js_ValueToObjectOrNull(cx, argv[1], &thisp))
-            return JS_FALSE;
-        thisv.setObjectOrNull(thisp);
+        if (argv[1].isNullOrUndefined()) {
+            thisv.setUndefined();
+        } else {
+            JSObject *thisObj;
+            if (!js_ValueToObjectOrNull(cx, argv[1], &thisObj))
+                return JS_FALSE;
+            JS_ASSERT(thisObj);
+            thisv.setObject(*thisObj);
+        }
     } else {
-        thisv.setNull();
+        thisv.setUndefined();
     }
 
     /*
      * For all but REDUCE, we call with 3 args (value, index, array). REDUCE
      * requires 4 args (accum, value, index, array).
      */
     argc = 3 + REDUCE_MODE(mode);
 
--- a/js/src/jsemit.cpp
+++ b/js/src/jsemit.cpp
@@ -6520,22 +6520,20 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
          * First, emit code for the left operand to evaluate the callable or
          * constructable object expression.
          *
          * For operator new applied to other expressions than E4X ones, we emit
          * JSOP_GETPROP instead of JSOP_CALLPROP, etc. This is necessary to
          * interpose the lambda-initialized method read barrier -- see the code
          * in jsinterp.cpp for JSOP_LAMBDA followed by JSOP_{SET,INIT}PROP.
          *
-         * Then (or in a call case that has no explicit reference-base object)
-         * we emit JSOP_NULL as a placeholder local GC root to hold the |this|
-         * parameter: in the operator new case, the newborn instance; in the
-         * base-less call case, a cookie meaning "use the global object as the
-         * |this| value" (or in ES5 strict mode, "use undefined", so we should
-         * use JSOP_PUSH instead of JSOP_NULL -- see bug 514570).
+         * Then (or in a call case that has no explicit reference-base
+         * object) we emit JSOP_PUSH to produce the |this| slot required
+         * for calls (which non-strict mode functions will box into the
+         * global object).
          */
         pn2 = pn->pn_head;
         switch (pn2->pn_type) {
           case TOK_NAME:
             if (!EmitNameOp(cx, cg, pn2, callop))
                 return JS_FALSE;
             break;
           case TOK_DOT:
@@ -6547,32 +6545,28 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
             if (!EmitElemOp(cx, pn2, callop ? JSOP_CALLELEM : JSOP_GETELEM, cg))
                 return JS_FALSE;
             break;
           case TOK_UNARYOP:
 #if JS_HAS_XML_SUPPORT
             if (pn2->pn_op == JSOP_XMLNAME) {
                 if (!EmitXMLName(cx, pn2, JSOP_CALLXMLNAME, cg))
                     return JS_FALSE;
-                callop = true;          /* suppress JSOP_NULL after */
+                callop = true;          /* suppress JSOP_PUSH after */
                 break;
             }
 #endif
             /* FALL THROUGH */
           default:
-            /*
-             * Push null as a placeholder for the global object, per ECMA-262
-             * 11.2.3 step 6.
-             */
             if (!js_EmitTree(cx, cg, pn2))
                 return JS_FALSE;
-            callop = false;             /* trigger JSOP_NULL after */
+            callop = false;             /* trigger JSOP_PUSH after */
             break;
         }
-        if (!callop && js_Emit1(cx, cg, JSOP_NULL) < 0)
+        if (!callop && js_Emit1(cx, cg, JSOP_PUSH) < 0)
             return JS_FALSE;
 
         /* Remember start of callable-object bytecode for decompilation hint. */
         off = top;
 
         /*
          * Emit code for each argument in order, then emit the JSOP_*CALL or
          * JSOP_NEW bytecode with a two-byte immediate telling how many args
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -2253,37 +2253,42 @@ js_fun_call(JSContext *cx, uintN argc, V
                                      js_Function_str, js_call_str,
                                      bytes);
             }
         }
         return JS_FALSE;
     }
 
     Value *argv = vp + 2;
+    Value thisv;
     if (argc == 0) {
         /* Call fun with its global object as the 'this' param if no args. */
-        obj = NULL;
+        thisv.setUndefined();
     } else {
         /* Otherwise convert the first arg to 'this' and skip over it. */
-        if (argv[0].isObject())
-            obj = &argv[0].toObject();
-        else if (!js_ValueToObjectOrNull(cx, argv[0], &obj))
-            return JS_FALSE;
+        if (argv[0].isNullOrUndefined()) {
+            thisv.setUndefined();
+        } else {
+            if (!js_ValueToObjectOrNull(cx, argv[0], &obj))
+                return JS_FALSE;
+            JS_ASSERT(obj);
+            thisv.setObject(*obj);
+        }
         argc--;
         argv++;
     }
 
     /* Allocate stack space for fval, obj, and the args. */
     InvokeArgsGuard args;
     if (!cx->stack().pushInvokeArgs(cx, argc, &args))
         return JS_FALSE;
 
-    /* Push fval, obj, and the args. */
+    /* Push fval, thisv, and the args. */
     args.callee() = fval;
-    args.thisv().setObjectOrNull(obj);
+    args.thisv() = thisv;
     memcpy(args.argv(), argv, argc * sizeof *argv);
 
     bool ok = Invoke(cx, args, 0);
     *vp = args.rval();
     return ok;
 }
 
 namespace {
@@ -2355,34 +2360,40 @@ js_fun_apply(JSContext *cx, uintN argc, 
             length = jsuint(lenval.toInt32()); /* jsuint cast does ToUint32 */
         } else {
             JS_STATIC_ASSERT(sizeof(jsuint) == sizeof(uint32_t));
             if (!ValueToECMAUint32(cx, lenval, (uint32_t *)&length))
                 return false;
         }
     }
 
+
     /* Convert the first arg to 'this' and skip over it. */
-    if (vp[2].isObject())
-        obj = &vp[2].toObject();
-    else if (!js_ValueToObjectOrNull(cx, vp[2], &obj))
-        return JS_FALSE;
+    Value thisv;
+    if (vp[2].isNullOrUndefined()) {
+        thisv.setUndefined();
+    } else {
+        if (!js_ValueToObjectOrNull(cx, vp[2], &obj))
+            return JS_FALSE;
+        JS_ASSERT(obj);
+        thisv.setObject(*obj);
+    }
 
     LeaveTrace(cx);
 
     /* Step 6. */
     uintN n = uintN(JS_MIN(length, JS_ARGS_LENGTH_MAX));
 
     InvokeArgsGuard args;
     if (!cx->stack().pushInvokeArgs(cx, n, &args))
         return false;
 
     /* Push fval, obj, and aobj's elements as args. */
     args.callee() = fval;
-    args.thisv().setObjectOrNull(obj);
+    args.thisv() = thisv;
 
     /* Steps 7-8. */
     if (aobj && aobj->isArguments() && !aobj->isArgsLengthOverridden()) {
         /*
          * Two cases, two loops: note how in the case of an active stack frame
          * backing aobj, even though we copy from fp->argv, we still must check
          * aobj->getArgsElement(i) for a hole, to handle a delete on the
          * corresponding arguments element. See args_delProperty.
@@ -2520,25 +2531,25 @@ CallOrConstructBoundFunction(JSContext *
     args.callee().setObject(*target);
 
     if (!constructing) {
         /*
          * FIXME Pass boundThis directly without boxing!  This will go away
          *       very shortly when this-boxing only occurs for non-strict
          *       functions, callee-side, in bug 514570.
          */
-        JSObject *boundThisObj;
-        if (boundThis.isObjectOrNull()) {
-            boundThisObj = boundThis.toObjectOrNull();
+        if (boundThis.isNullOrUndefined()) {
+            args.thisv().setUndefined();
         } else {
+            JSObject *boundThisObj;
             if (!js_ValueToObjectOrNull(cx, boundThis, &boundThisObj))
                 return false;
+            JS_ASSERT(boundThisObj);
+            args.thisv().setObject(*boundThisObj);
         }
-
-        args.thisv() = ObjectOrNullValue(boundThisObj);
     }
 
     if (constructing ? !InvokeConstructor(cx, args) : !Invoke(cx, args, 0))
         return false;
 
     *vp = args.rval();
     return true;
 }
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -493,17 +493,17 @@ bool
 ComputeThisFromArgv(JSContext *cx, Value *argv)
 {
     /*
      * Check for SynthesizeFrame poisoning and fast constructors which
      * didn't check their vp properly.
      */
     JS_ASSERT(!argv[-1].isMagic());
 
-    if (argv[-1].isNull())
+    if (argv[-1].isNullOrUndefined())
         return ComputeGlobalThis(cx, argv);
 
     if (!argv[-1].isObject())
         return !!js_PrimitiveToObject(cx, &argv[-1]);
 
     JS_ASSERT(IsSaneThisObject(argv[-1].toObject()));
     return true;
 }
@@ -673,17 +673,17 @@ Invoke(JSContext *cx, const CallArgs &ar
         }
         return CallJSNative(cx, clasp->call, args.argc(), args.base());
     }
 
     /* Invoke native functions. */
     JSFunction *fun = callee.getFunctionPrivate();
     JS_ASSERT_IF(flags & JSINVOKE_CONSTRUCT, !fun->isConstructor());
     if (fun->isNative()) {
-        JS_ASSERT(args.thisv().isObjectOrNull() || PrimitiveThisTest(fun, args.thisv()));
+        JS_ASSERT(args.thisv().isObject() || args.thisv().isUndefined() || PrimitiveThisTest(fun, args.thisv()));
         return CallJSNative(cx, fun->u.n.native, args.argc(), args.base());
     }
 
     /* Handle the empty-script special case. */
     JSScript *script = fun->script();
     if (JS_UNLIKELY(script->isEmpty())) {
         if (flags & JSINVOKE_CONSTRUCT) {
             JSObject *obj = js_CreateThisForFunction(cx, &callee);
@@ -4234,17 +4234,17 @@ BEGIN_CASE(JSOP_CALLPROP)
     }
 
   end_callprop:
     /* Wrap primitive lval in object clothing if necessary. */
     if (lval.isPrimitive()) {
         /* FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=412571 */
         JSObject *funobj;
         if (!IsFunctionObject(rval, &funobj) ||
-            !PrimitiveThisTest(GET_FUNCTION_PRIVATE(cx, funobj), lval)) {
+            !PrimitiveThisTest(funobj->getFunctionPrivate(), lval)) {
             if (!js_PrimitiveToObject(cx, &regs.sp[-1]))
                 goto error;
         }
     }
 #if JS_HAS_NO_SUCH_METHOD
     if (JS_UNLIKELY(rval.isUndefined())) {
         LOAD_ATOM(0, atom);
         regs.sp[-2].setString(ATOM_TO_STRING(atom));
@@ -4716,17 +4716,17 @@ BEGIN_CASE(JSOP_APPLY)
             TRACE_0(EnterFrame);
 
             /* Load first op and dispatch it (safe since JSOP_STOP). */
             op = (JSOp) *regs.pc;
             JS_ASSERT(op == JSOP_BEGIN);
             DO_OP();
         }
 
-        JS_ASSERT(vp[1].isObjectOrNull() || PrimitiveThisTest(newfun, vp[1]));
+        JS_ASSERT(vp[1].isObject() || vp[1].isUndefined() || PrimitiveThisTest(newfun, vp[1]));
 
         Probes::enterJSFun(cx, newfun);
         JSBool ok = CallJSNative(cx, newfun->u.n.native, argc, vp);
         Probes::exitJSFun(cx, newfun);
         regs.sp = vp + 1;
         if (!ok)
             goto error;
         TRACE_0(NativeCallComplete);
@@ -4761,25 +4761,27 @@ END_CASE(JSOP_SETCALL)
 #define SLOW_PUSH_THISV(cx, obj)                                            \
     JS_BEGIN_MACRO                                                          \
         Class *clasp;                                                       \
         JSObject *thisp = obj;                                              \
         if (!thisp->getParent() ||                                          \
             (clasp = thisp->getClass()) == &js_CallClass ||                 \
             clasp == &js_BlockClass ||                                      \
             clasp == &js_DeclEnvClass) {                                    \
-            /* Normal case: thisp is global or an activation record. */     \
-            /* Callee determines |this|. */                                 \
-            thisp = NULL;                                                   \
+            /* Push the ImplicitThisValue for the Environment Record */     \
+            /* associated with obj. See ES5 sections 10.2.1.1.6 and  */     \
+            /* 10.2.1.2.6 (ImplicitThisValue) and section 11.2.3     */     \
+            /* (Function Calls). */                                         \
+            PUSH_UNDEFINED();                                               \
         } else {                                                            \
             thisp = thisp->thisObject(cx);                                  \
             if (!thisp)                                                     \
                 goto error;                                                 \
+            PUSH_OBJECT(*thisp);                                            \
         }                                                                   \
-        PUSH_OBJECT_OR_NULL(thisp);                                         \
     JS_END_MACRO
 
 BEGIN_CASE(JSOP_GETGNAME)
 BEGIN_CASE(JSOP_CALLGNAME)
 BEGIN_CASE(JSOP_NAME)
 BEGIN_CASE(JSOP_CALLNAME)
 {
     JSObject *obj = &regs.fp->scopeChain();
@@ -4814,17 +4816,17 @@ BEGIN_CASE(JSOP_CALLNAME)
 #if DEBUG
         Class *clasp;
         JS_ASSERT(!obj->getParent() ||
                   (clasp = obj->getClass()) == &js_CallClass ||
                   clasp == &js_BlockClass ||
                   clasp == &js_DeclEnvClass);
 #endif
         if (op == JSOP_CALLNAME || op == JSOP_CALLGNAME)
-            PUSH_NULL();
+            PUSH_UNDEFINED();
         len = JSOP_NAME_LENGTH;
         DO_NEXT_OP(len);
     }
 
     jsid id;
     id = ATOM_TO_JSID(atom);
     JSProperty *prop;
     if (!js_FindPropertyHelper(cx, id, true, &obj, &obj2, &prop))
@@ -5175,17 +5177,17 @@ END_CASE(JSOP_ARGCNT)
 BEGIN_CASE(JSOP_GETARG)
 BEGIN_CASE(JSOP_CALLARG)
 {
     uint32 slot = GET_ARGNO(regs.pc);
     JS_ASSERT(slot < regs.fp->numFormalArgs());
     METER_SLOT_OP(op, slot);
     PUSH_COPY(argv[slot]);
     if (op == JSOP_CALLARG)
-        PUSH_NULL();
+        PUSH_UNDEFINED();
 }
 END_CASE(JSOP_GETARG)
 
 BEGIN_CASE(JSOP_SETARG)
 {
     uint32 slot = GET_ARGNO(regs.pc);
     JS_ASSERT(slot < regs.fp->numFormalArgs());
     METER_SLOT_OP(op, slot);
@@ -5201,17 +5203,17 @@ BEGIN_CASE(JSOP_GETLOCAL)
 }
 END_CASE(JSOP_GETLOCAL)
 
 BEGIN_CASE(JSOP_CALLLOCAL)
 {
     uint32 slot = GET_SLOTNO(regs.pc);
     JS_ASSERT(slot < script->nslots);
     PUSH_COPY(regs.fp->slots()[slot]);
-    PUSH_NULL();
+    PUSH_UNDEFINED();
 }
 END_CASE(JSOP_CALLLOCAL)
 
 BEGIN_CASE(JSOP_SETLOCAL)
 {
     uint32 slot = GET_SLOTNO(regs.pc);
     JS_ASSERT(slot < script->nslots);
     regs.fp->slots()[slot] = regs.sp[-1];
@@ -5225,17 +5227,17 @@ BEGIN_CASE(JSOP_CALLUPVAR)
 
     uintN index = GET_UINT16(regs.pc);
     JS_ASSERT(index < uva->length);
 
     const Value &rval = GetUpvar(cx, script->staticLevel, uva->vector[index]);
     PUSH_COPY(rval);
 
     if (op == JSOP_CALLUPVAR)
-        PUSH_NULL();
+        PUSH_UNDEFINED();
 }
 END_CASE(JSOP_GETUPVAR)
 
 BEGIN_CASE(JSOP_GETUPVAR_DBG)
 BEGIN_CASE(JSOP_CALLUPVAR_DBG)
 {
     JSFunction *fun = regs.fp->fun();
     JS_ASSERT(FUN_KIND(fun) == JSFUN_INTERPRETED);
@@ -5267,44 +5269,44 @@ BEGIN_CASE(JSOP_CALLUPVAR_DBG)
     /* Minimize footprint with generic code instead of NATIVE_GET. */
     obj2->dropProperty(cx, prop);
     Value *vp = regs.sp;
     PUSH_NULL();
     if (!obj->getProperty(cx, id, vp))
         goto error;
 
     if (op == JSOP_CALLUPVAR_DBG)
-        PUSH_NULL();
+        PUSH_UNDEFINED();
 }
 END_CASE(JSOP_GETUPVAR_DBG)
 
 BEGIN_CASE(JSOP_GETFCSLOT)
 BEGIN_CASE(JSOP_CALLFCSLOT)
 {
     JS_ASSERT(regs.fp->isFunctionFrame() && !regs.fp->isEvalFrame());
     uintN index = GET_UINT16(regs.pc);
     JSObject *obj = &argv[-2].toObject();
 
     JS_ASSERT(index < obj->getFunctionPrivate()->u.i.nupvars);
     PUSH_COPY(obj->getFlatClosureUpvar(index));
     if (op == JSOP_CALLFCSLOT)
-        PUSH_NULL();
+        PUSH_UNDEFINED();
 }
 END_CASE(JSOP_GETFCSLOT)
 
 BEGIN_CASE(JSOP_GETGLOBAL)
 BEGIN_CASE(JSOP_CALLGLOBAL)
 {
     uint32 slot = GET_SLOTNO(regs.pc);
     slot = script->getGlobalSlot(slot);
     JSObject *obj = regs.fp->scopeChain().getGlobal();
     JS_ASSERT(obj->containsSlot(slot));
     PUSH_COPY(obj->getSlot(slot));
     if (op == JSOP_CALLGLOBAL)
-        PUSH_NULL();
+        PUSH_UNDEFINED();
 }
 END_CASE(JSOP_GETGLOBAL)
 
 BEGIN_CASE(JSOP_FORGLOBAL)
 {
     Value rval;
     if (!IteratorNext(cx, &regs.sp[-1].toObject(), &rval))
         goto error;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -5921,22 +5921,24 @@ js_SetClassPrototype(JSContext *cx, JSOb
 
 JSBool
 js_PrimitiveToObject(JSContext *cx, Value *vp)
 {
     Value v = *vp;
     JS_ASSERT(v.isPrimitive());
 
     Class *clasp;
-    if (v.isNumber())
+    if (v.isNumber()) {
         clasp = &js_NumberClass;
-    else if (v.isString())
+    } else if (v.isString()) {
         clasp = &js_StringClass;
-    else
+    } else {
+        JS_ASSERT(v.isBoolean());
         clasp = &js_BooleanClass;
+    }
 
     JSObject *obj = NewBuiltinClassInstance(cx, clasp);
     if (!obj)
         return JS_FALSE;
 
     obj->setPrimitiveThis(v);
     vp->setObject(*obj);
     return JS_TRUE;
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -4112,18 +4112,18 @@ Decompile(SprintStack *ss, jsbytecode *p
                     pc += len;
                     if (*pc == JSOP_BLOCKCHAIN) {
                         pc += JSOP_BLOCKCHAIN_LENGTH;
                     } else if (*pc == JSOP_NULLBLOCKCHAIN) {
                         pc += JSOP_NULLBLOCKCHAIN_LENGTH;
                     } else {
                         JS_NOT_REACHED("should see block chain operation");
                     }
-                    LOCAL_ASSERT(*pc == JSOP_NULL);
-                    pc += JSOP_NULL_LENGTH;
+                    LOCAL_ASSERT(*pc == JSOP_PUSH);
+                    pc += JSOP_PUSH_LENGTH;
                     LOCAL_ASSERT(*pc == JSOP_CALL);
                     LOCAL_ASSERT(GET_ARGC(pc) == 0);
                     len = JSOP_CALL_LENGTH;
 
                     /*
                      * Arrange to parenthesize this genexp unless:
                      *
                      *  1. It is the complete expression consumed by a control
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -851,17 +851,17 @@ Class js_StringClass = {
             if (!str)                                                         \
                 return JS_FALSE;                                              \
         }                                                                     \
     JS_END_MACRO
 
 static JSString *
 NormalizeThis(JSContext *cx, Value *vp)
 {
-    if (vp[1].isNull() && (!ComputeThisFromVp(cx, vp) || vp[1].isNull()))
+    if (vp[1].isNullOrUndefined() && !ComputeThisFromVp(cx, vp))
         return NULL;
 
     /*
      * String.prototype.{toString,toSource,valueOf} throw a TypeError if the
      * this-argument is not a string or a String object. So those methods use
      * js::GetPrimitiveThis which provides that behavior.
      *
      * By standard, the rest of the String methods must ToString the
@@ -2095,17 +2095,17 @@ FindReplaceLength(JSContext *cx, RegExpS
          * We grab up stack space to keep the newborn strings GC-rooted.
          */
         uintN p = res->getParenCount();
         uintN argc = 1 + p + 2;
 
         InvokeSessionGuard &session = rdata.session;
         if (!session.started()) {
             Value lambdav = ObjectValue(*lambda);
-            if (!rdata.session.start(cx, lambdav, NullValue(), argc))
+            if (!session.start(cx, lambdav, UndefinedValue(), argc))
                 return false;
         }
 
         PreserveRegExpStatics staticsGuard(res);
         if (!staticsGuard.init(cx))
             return false;
 
         /* Push $&, $1, $2, ... */
@@ -2404,17 +2404,17 @@ str_replace_flat_lambda(JSContext *cx, u
 
     /* lambda(matchStr, matchStart, textstr) */
     static const uint32 lambdaArgc = 3;
     if (!cx->stack().pushInvokeArgs(cx, lambdaArgc, &rdata.singleShot))
         return false;
 
     CallArgs &args = rdata.singleShot;
     args.callee().setObject(*rdata.lambda);
-    args.thisv().setNull();
+    args.thisv().setUndefined();
 
     Value *sp = args.argv();
     sp[0].setString(matchStr);
     sp[1].setInt32(fm.match());
     sp[2].setString(rdata.str);
 
     if (!Invoke(cx, rdata.singleShot, 0))
         return false;
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -7058,22 +7058,16 @@ LeaveTree(TraceMonitor *tm, TracerState&
         JS_ASSERT(check_ngslots == ngslots);
         globalTypeMap = typeMap.data();
     }
 
     /* Write back interned globals. */
     JS_ASSERT(state.eos == state.stackBase + MAX_NATIVE_STACK_SLOTS);
     JSObject* globalObj = outermostTree->globalObj;
     FlushNativeGlobalFrame(cx, globalObj, state.eos, ngslots, gslots, globalTypeMap);
-#ifdef DEBUG
-    /* Verify that our state restoration worked. */
-    for (JSStackFrame* fp = cx->fp(); fp; fp = fp->prev()) {
-        JS_ASSERT_IF(fp->isFunctionFrame(), fp->thisValue().isObjectOrNull());
-    }
-#endif
 
 #ifdef JS_JIT_SPEW
     if (innermost->exitType != TIMEOUT_EXIT)
         AUDIT(sideExitIntoInterpreter);
     else
         AUDIT(timeoutIntoInterpreter);
 #endif
 
@@ -10058,20 +10052,20 @@ TraceRecorder::getThis(LIns*& this_ins)
 
         this_ins = INS_CONSTOBJ(&fp->thisValue().toObject());
         return RECORD_CONTINUE;
     }
 
     JS_ASSERT(fp->callee().getGlobal() == globalObj);    
     const Value& thisv = fp->thisValue();
 
-    if (!thisv.isNull()) {
+    if (!thisv.isUndefined()) {
         /*
          * fp->argv[-1] has already been computed. Since the type-specialization
-         * of traces distinguishes between null and objects, the same will be
+         * of traces distinguishes between |undefined| and objects, the same will be
          * true at run time (or we won't get this far).
          */
         this_ins = get(&fp->thisValue());
         return RECORD_CONTINUE;
     }
 
     /*
      * Compute 'this' now. The result is globalObj->thisObject(),
@@ -13134,31 +13128,31 @@ TraceRecorder::record_JSOP_CALLNAME()
 {
     JSObject* obj = &cx->fp()->scopeChain();
     if (obj != globalObj) {
         Value* vp;
         LIns* ins;
         NameResult nr;
         CHECK_STATUS_A(scopeChainProp(obj, vp, ins, nr));
         stack(0, ins);
-        stack(1, INS_NULL());
+        stack(1, INS_UNDEFINED());
         return ARECORD_CONTINUE;
     }
 
     LIns* obj_ins = INS_CONSTOBJ(globalObj);
     JSObject* obj2;
     PCVal pcval;
 
     CHECK_STATUS_A(test_property_cache(obj, obj_ins, obj2, pcval));
 
     if (pcval.isNull() || !pcval.isFunObj())
         RETURN_STOP_A("callee is not an object");
 
     stack(0, INS_CONSTOBJ(&pcval.toFunObj()));
-    stack(1, INS_NULL());
+    stack(1, INS_UNDEFINED());
     return ARECORD_CONTINUE;
 }
 
 JS_DEFINE_CALLINFO_5(extern, UINT32, GetUpvarArgOnTrace, CONTEXT, UINT32, INT32, UINT32,
                      DOUBLEPTR, 0, ACCSET_STORE_ANY)
 JS_DEFINE_CALLINFO_5(extern, UINT32, GetUpvarVarOnTrace, CONTEXT, UINT32, INT32, UINT32,
                      DOUBLEPTR, 0, ACCSET_STORE_ANY)
 JS_DEFINE_CALLINFO_5(extern, UINT32, GetUpvarStackOnTrace, CONTEXT, UINT32, INT32, UINT32,
@@ -13277,17 +13271,17 @@ TraceRecorder::record_JSOP_GETUPVAR()
     stack(0, upvar_ins);
     return ARECORD_CONTINUE;
 }
 
 JS_REQUIRES_STACK AbortableRecordingStatus
 TraceRecorder::record_JSOP_CALLUPVAR()
 {
     CHECK_STATUS_A(record_JSOP_GETUPVAR());
-    stack(1, INS_NULL());
+    stack(1, INS_UNDEFINED());
     return ARECORD_CONTINUE;
 }
 
 JS_REQUIRES_STACK AbortableRecordingStatus
 TraceRecorder::record_JSOP_GETFCSLOT()
 {
     JSObject& callee = cx->fp()->callee();
     LIns* callee_ins = get(&cx->fp()->calleeValue());
@@ -13301,17 +13295,17 @@ TraceRecorder::record_JSOP_GETFCSLOT()
     stack(0, v_ins);
     return ARECORD_CONTINUE;
 }
 
 JS_REQUIRES_STACK AbortableRecordingStatus
 TraceRecorder::record_JSOP_CALLFCSLOT()
 {
     CHECK_STATUS_A(record_JSOP_GETFCSLOT());
-    stack(1, INS_NULL());
+    stack(1, INS_UNDEFINED());
     return ARECORD_CONTINUE;
 }
 
 JS_REQUIRES_STACK RecordingStatus
 TraceRecorder::guardCallee(Value& callee)
 {
     JSObject& callee_obj = callee.toObject();
     JS_ASSERT(callee_obj.isFunction());
@@ -15949,26 +15943,26 @@ TraceRecorder::record_JSOP_INDEXBASE3()
     return ARECORD_CONTINUE;
 }
 
 JS_REQUIRES_STACK AbortableRecordingStatus
 TraceRecorder::record_JSOP_CALLLOCAL()
 {
     uintN slot = GET_SLOTNO(cx->regs->pc);
     stack(0, var(slot));
-    stack(1, INS_NULL());
+    stack(1, INS_UNDEFINED());
     return ARECORD_CONTINUE;
 }
 
 JS_REQUIRES_STACK AbortableRecordingStatus
 TraceRecorder::record_JSOP_CALLARG()
 {
     uintN slot = GET_ARGNO(cx->regs->pc);
     stack(0, arg(slot));
-    stack(1, INS_NULL());
+    stack(1, INS_UNDEFINED());
     return ARECORD_CONTINUE;
 }
 
 JS_REQUIRES_STACK AbortableRecordingStatus
 TraceRecorder::record_JSOP_BINDGNAME()
 {
     stack(0, INS_CONSTOBJ(globalObj));
     return ARECORD_CONTINUE;
@@ -16162,17 +16156,17 @@ JS_REQUIRES_STACK AbortableRecordingStat
 TraceRecorder::record_JSOP_CALLGLOBAL()
 {
     uint32 slot = cx->fp()->script()->getGlobalSlot(GET_SLOTNO(cx->regs->pc));
     if (!lazilyImportGlobalSlot(slot))
          RETURN_STOP_A("lazy import of global slot failed");
 
     Value &v = globalObj->getSlotRef(slot);
     stack(0, get(&v));
-    stack(1, INS_NULL());
+    stack(1, INS_UNDEFINED());
     return ARECORD_CONTINUE;
 }
 
 JS_REQUIRES_STACK AbortableRecordingStatus
 TraceRecorder::record_JSOP_GLOBALDEC()
 {
     uint32 slot = cx->fp()->script()->getGlobalSlot(GET_SLOTNO(cx->regs->pc));
     if (!lazilyImportGlobalSlot(slot))
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -1203,17 +1203,17 @@ mjit::Compiler::generateMethod()
           }
           END_CASE(JSOP_NEW)
 
           BEGIN_CASE(JSOP_GETARG)
           BEGIN_CASE(JSOP_CALLARG)
           {
             jsop_getarg(GET_SLOTNO(PC));
             if (op == JSOP_CALLARG)
-                frame.push(NullValue());
+                frame.push(UndefinedValue());
           }
           END_CASE(JSOP_GETARG)
 
           BEGIN_CASE(JSOP_BINDGNAME)
             jsop_bindgname();
           END_CASE(JSOP_BINDGNAME)
 
           BEGIN_CASE(JSOP_SETARG)
@@ -1492,17 +1492,17 @@ mjit::Compiler::generateMethod()
             // obj->getFlatClosureUpvars()
             Address upvarAddress(reg, offsetof(JSObject, fslots) + 
                                       JSObject::JSSLOT_FLAT_CLOSURE_UPVARS * sizeof(Value));
             masm.loadPrivate(upvarAddress, reg);
             // push ((Value *) reg)[index]
             frame.freeReg(reg);
             frame.push(Address(reg, index * sizeof(Value)));
             if (op == JSOP_CALLFCSLOT)
-                frame.push(NullValue());
+                frame.push(UndefinedValue());
           }
           END_CASE(JSOP_CALLFCSLOT)
 
           BEGIN_CASE(JSOP_ARGSUB)
             prepareStubCall(Uses(0));
             masm.move(Imm32(GET_ARGNO(PC)), Registers::ArgReg1);
             stubCall(stubs::ArgSub);
             frame.pushSynced();
@@ -1531,17 +1531,17 @@ mjit::Compiler::generateMethod()
           BEGIN_CASE(JSOP_RETRVAL)
             emitReturn(NULL);
           END_CASE(JSOP_RETRVAL)
 
           BEGIN_CASE(JSOP_GETGNAME)
           BEGIN_CASE(JSOP_CALLGNAME)
             jsop_getgname(fullAtomIndex(PC));
             if (op == JSOP_CALLGNAME)
-                frame.push(NullValue());
+                frame.push(UndefinedValue());
           END_CASE(JSOP_GETGNAME)
 
           BEGIN_CASE(JSOP_SETGNAME)
             jsop_setgname(fullAtomIndex(PC));
           END_CASE(JSOP_SETGNAME)
 
           BEGIN_CASE(JSOP_REGEXP)
           {
@@ -1566,17 +1566,17 @@ mjit::Compiler::generateMethod()
             JSUpvarArray *uva = script->upvars();
             JS_ASSERT(index < uva->length);
 
             prepareStubCall(Uses(0));
             masm.move(Imm32(uva->vector[index].asInteger()), Registers::ArgReg1);
             stubCall(stubs::GetUpvar);
             frame.pushSynced();
             if (op == JSOP_CALLUPVAR)
-                frame.push(NullValue());
+                frame.push(UndefinedValue());
           }
           END_CASE(JSOP_CALLUPVAR)
 
           BEGIN_CASE(JSOP_UINT24)
             frame.push(Value(Int32Value((int32_t) GET_UINT24(PC))));
           END_CASE(JSOP_UINT24)
 
           BEGIN_CASE(JSOP_CALLELEM)
@@ -1602,17 +1602,17 @@ mjit::Compiler::generateMethod()
           END_CASE(JSOP_ENTERBLOCK);
 
           BEGIN_CASE(JSOP_LEAVEBLOCK)
             leaveBlock();
           END_CASE(JSOP_LEAVEBLOCK)
 
           BEGIN_CASE(JSOP_CALLLOCAL)
             frame.pushLocal(GET_SLOTNO(PC));
-            frame.push(NullValue());
+            frame.push(UndefinedValue());
           END_CASE(JSOP_CALLLOCAL)
 
           BEGIN_CASE(JSOP_INT8)
             frame.push(Value(Int32Value(GET_INT8(PC))));
           END_CASE(JSOP_INT8)
 
           BEGIN_CASE(JSOP_INT32)
             frame.push(Value(Int32Value(GET_INT32(PC))));
@@ -1677,17 +1677,17 @@ mjit::Compiler::generateMethod()
             jsop_unbrand();
             frame.pop();
           END_CASE(JSOP_UNBRANDTHIS)
 
           BEGIN_CASE(JSOP_GETGLOBAL)
           BEGIN_CASE(JSOP_CALLGLOBAL)
             jsop_getglobal(GET_SLOTNO(PC));
             if (op == JSOP_CALLGLOBAL)
-                frame.push(NullValue());
+                frame.push(UndefinedValue());
           END_CASE(JSOP_GETGLOBAL)
 
           BEGIN_CASE(JSOP_SETGLOBAL)
             jsop_setglobal(GET_SLOTNO(PC));
           END_CASE(JSOP_SETGLOBAL)
 
           BEGIN_CASE(JSOP_INCGLOBAL)
           BEGIN_CASE(JSOP_DECGLOBAL)
@@ -3309,28 +3309,28 @@ mjit::Compiler::jsop_getarg(uint32 index
     frame.push(Address(JSFrameReg, JSStackFrame::offsetOfFormalArg(fun, index)));
 }
 
 void
 mjit::Compiler::jsop_this()
 {
     Address thisvAddr(JSFrameReg, JSStackFrame::offsetOfThis(fun));
     if (0 && !script->strictModeCode) {
-        Jump null = masm.testNull(Assembler::Equal, thisvAddr);
+        Jump null = masm.testUndefined(Assembler::Equal, thisvAddr);
         stubcc.linkExit(null, Uses(1));
         stubcc.leave();
         stubcc.call(stubs::ComputeThis);
         stubcc.rejoin(Changes(1));
 
         RegisterID reg = frame.allocReg();
         masm.loadPayload(thisvAddr, reg);
         frame.pushTypedPayload(JSVAL_TYPE_OBJECT, reg);
     } else {
         frame.push(thisvAddr);
-        Jump null = frame.testNull(Assembler::Equal, frame.peek(-1));
+        Jump null = frame.testUndefined(Assembler::Equal, frame.peek(-1));
         stubcc.linkExit(null, Uses(1));
         stubcc.leave();
         stubcc.call(stubs::This);
         stubcc.rejoin(Changes(1));
     }
 }
 
 void
--- a/js/src/methodjit/FrameState-inl.h
+++ b/js/src/methodjit/FrameState-inl.h
@@ -563,16 +563,25 @@ FrameState::testNull(Assembler::Conditio
 {
     JS_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual);
     if (shouldAvoidTypeRemat(fe))
         return masm.testNull(cond, addressOf(fe));
     return masm.testNull(cond, tempRegForType(fe));
 }
 
 inline JSC::MacroAssembler::Jump
+FrameState::testUndefined(Assembler::Condition cond, FrameEntry *fe)
+{
+    JS_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual);
+    if (shouldAvoidTypeRemat(fe))
+        return masm.testUndefined(cond, addressOf(fe));
+    return masm.testUndefined(cond, tempRegForType(fe));
+}
+
+inline JSC::MacroAssembler::Jump
 FrameState::testInt32(Assembler::Condition cond, FrameEntry *fe)
 {
     JS_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual);
     if (shouldAvoidTypeRemat(fe))
         return masm.testInt32(cond, addressOf(fe));
     return masm.testInt32(cond, tempRegForType(fe));
 }
 
--- a/js/src/methodjit/FrameState.h
+++ b/js/src/methodjit/FrameState.h
@@ -655,53 +655,59 @@ class FrameState
     inline void forgetType(FrameEntry *fe);
 
     /*
      * Discards a FrameEntry, tricking the FS into thinking it's synced.
      */
     void discardFe(FrameEntry *fe);
 
     /*
-     * Helper function. Tests if a slot's type is null. Condition should
+     * Helper function. Tests if a slot's type is null. Condition must
      * be Equal or NotEqual.
      */
     inline Jump testNull(Assembler::Condition cond, FrameEntry *fe);
 
     /*
-     * Helper function. Tests if a slot's type is an integer. Condition should
+     * Helper function. Tests if a slot's type is undefined. Condition must
+     * be Equal or NotEqual.
+     */
+    inline Jump testUndefined(Assembler::Condition cond, FrameEntry *fe);
+
+    /*
+     * Helper function. Tests if a slot's type is an integer. Condition must
      * be Equal or NotEqual.
      */
     inline Jump testInt32(Assembler::Condition cond, FrameEntry *fe);
 
     /*
-     * Helper function. Tests if a slot's type is a double. Condition should
+     * Helper function. Tests if a slot's type is a double. Condition must
      * be Equal or Not Equal.
      */
     inline Jump testDouble(Assembler::Condition cond, FrameEntry *fe);
 
     /*
-     * Helper function. Tests if a slot's type is a boolean. Condition should
+     * Helper function. Tests if a slot's type is a boolean. Condition must
      * be Equal or NotEqual.
      */
     inline Jump testBoolean(Assembler::Condition cond, FrameEntry *fe);
 
     /*
-     * Helper function. Tests if a slot's type is a string. Condition should
+     * Helper function. Tests if a slot's type is a string. Condition must
      * be Equal or NotEqual.
      */
     inline Jump testString(Assembler::Condition cond, FrameEntry *fe);
 
     /*
-     * Helper function. Tests if a slot's type is a non-funobj. Condition should
+     * Helper function. Tests if a slot's type is a non-funobj. Condition must
      * be Equal or NotEqual.
      */
     inline Jump testObject(Assembler::Condition cond, FrameEntry *fe);
 
     /*
-     * Helper function. Tests if a slot's type is primitve. Condition should
+     * Helper function. Tests if a slot's type is primitive. Condition must
      * be Equal or NotEqual.
      */
     inline Jump testPrimitive(Assembler::Condition cond, FrameEntry *fe);
 
     /*
      * Marks a register such that it cannot be spilled by the register
      * allocator. Any pinned registers must be unpinned at the end of the op,
      * no matter what. In addition, pinReg() can only be used on registers
--- a/js/src/methodjit/NunboxAssembler.h
+++ b/js/src/methodjit/NunboxAssembler.h
@@ -210,16 +210,24 @@ class Assembler : public BaseAssembler
     Jump testNull(Assembler::Condition cond, RegisterID reg) {
         return branch32(cond, reg, ImmTag(JSVAL_TAG_NULL));
     }
 
     Jump testNull(Assembler::Condition cond, Address address) {
         return branch32(cond, tagOf(address), ImmTag(JSVAL_TAG_NULL));
     }
 
+    Jump testUndefined(Assembler::Condition cond, RegisterID reg) {
+        return branch32(cond, reg, ImmTag(JSVAL_TAG_UNDEFINED));
+    }
+
+    Jump testUndefined(Assembler::Condition cond, Address address) {
+        return branch32(cond, tagOf(address), ImmTag(JSVAL_TAG_UNDEFINED));
+    }
+
     Jump testInt32(Assembler::Condition cond, RegisterID reg) {
         return branch32(cond, reg, ImmTag(JSVAL_TAG_INT32));
     }
 
     Jump testInt32(Assembler::Condition cond, Address address) {
         return branch32(cond, tagOf(address), ImmTag(JSVAL_TAG_INT32));
     }
 
--- a/js/src/methodjit/PunboxAssembler.h
+++ b/js/src/methodjit/PunboxAssembler.h
@@ -231,16 +231,25 @@ class Assembler : public BaseAssembler
         return branchPtr(cond, reg, ImmTag(JSVAL_SHIFTED_TAG_NULL));
     }
 
     Jump testNull(Assembler::Condition cond, Address address) {
         loadValue(address, Registers::ValueReg);
         return branchPtr(cond, Registers::ValueReg, Imm64(JSVAL_BITS(JSVAL_NULL)));
     }
 
+    Jump testUndefined(Assembler::Condition cond, RegisterID reg) {
+        return branchPtr(cond, reg, ImmTag(JSVAL_SHIFTED_TAG_UNDEFINED));
+    }
+
+    Jump testUndefined(Assembler::Condition cond, Address address) {
+        loadValue(address, Registers::ValueReg);
+        return branchPtr(cond, Registers::ValueReg, Imm64(JSVAL_BITS(JSVAL_VOID)));
+    }
+
     Jump testInt32(Assembler::Condition cond, RegisterID reg) {
         return branchPtr(cond, reg, ImmTag(JSVAL_SHIFTED_TAG_INT32));
     }
 
     Jump testInt32(Assembler::Condition cond, Address address) {
         loadValue(address, Registers::ValueReg);
         convertValueToType(Registers::ValueReg);
         return branchPtr(cond, Registers::ValueReg, ImmTag(JSVAL_SHIFTED_TAG_INT32));
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -366,17 +366,17 @@ NameOp(VMFrame &f, JSObject *obj, bool c
         Class *clasp;
         JS_ASSERT(!obj->getParent() ||
                   (clasp = obj->getClass()) == &js_CallClass ||
                   clasp == &js_BlockClass ||
                   clasp == &js_DeclEnvClass);
 #endif
         if (callname) {
             f.regs.sp++;
-            f.regs.sp[-1].setNull();
+            f.regs.sp[-1].setUndefined();
         }
         return obj;
     }
 
     jsid id;
     id = ATOM_TO_JSID(atom);
     JSProperty *prop;
     if (!js_FindPropertyHelper(cx, id, true, &obj, &obj2, &prop))
@@ -411,24 +411,25 @@ NameOp(VMFrame &f, JSObject *obj, bool c
     f.regs.sp[-1] = rval;
     if (callname) {
         Class *clasp;
         JSObject *thisp = obj;
         if (!thisp->getParent() ||
             (clasp = thisp->getClass()) == &js_CallClass ||
             clasp == &js_BlockClass ||
             clasp == &js_DeclEnvClass) {
-            thisp = NULL;
+            f.regs.sp++;
+            f.regs.sp[-1].setUndefined();
         } else {
             thisp = thisp->thisObject(cx);
             if (!thisp)
                 return NULL;
+            f.regs.sp++;
+            f.regs.sp[-1].setObject(*thisp);
         }
-        f.regs.sp++;
-        f.regs.sp[-1].setObjectOrNull(thisp);
     }
     return obj;
 }
 
 void JS_FASTCALL
 stubs::Name(VMFrame &f)
 {
     if (!NameOp(f, &f.fp()->scopeChain()))
@@ -2156,17 +2157,17 @@ stubs::CallProp(VMFrame &f, JSAtom *orig
     }
 
   end_callprop:
     /* Wrap primitive lval in object clothing if necessary. */
     if (lval.isPrimitive()) {
         /* FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=412571 */
         JSObject *funobj;
         if (!IsFunctionObject(rval, &funobj) ||
-            !PrimitiveThisTest(GET_FUNCTION_PRIVATE(cx, funobj), lval)) {
+            !PrimitiveThisTest(funobj->getFunctionPrivate(), lval)) {
             if (!js_PrimitiveToObject(cx, &regs.sp[-1]))
                 THROW();
         }
     }
 #if JS_HAS_NO_SUCH_METHOD
     if (JS_UNLIKELY(rval.isUndefined())) {
         regs.sp[-2].setString(ATOM_TO_STRING(origAtom));
         if (!js_OnUnknownMethod(cx, regs.sp - 2))