Bug 416601: property cache is properly disabled under with statements with generators. r=brendan a1.9=blocking1.9
authorigor@mir2.org
Fri, 15 Feb 2008 03:38:40 -0800
changeset 11758 8e023ae5c0cae924450698d1be6ef12e14df913b
parent 11757 4030429c22ca2411570fc02daa417290caa956e3
child 11759 da93b81df4cccdcbc1b9059663ed8a1fcb3351cb
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbrendan
bugs416601
milestone1.9b4pre
Bug 416601: property cache is properly disabled under with statements with generators. r=brendan a1.9=blocking1.9
js/src/jscntxt.c
js/src/jsemit.c
js/src/jsinterp.c
js/src/jsinterp.h
js/src/jsobj.c
js/src/jsobj.h
js/src/jsxdrapi.h
js/src/win32.order
--- a/js/src/jscntxt.c
+++ b/js/src/jscntxt.c
@@ -893,19 +893,22 @@ js_ReportOutOfMemory(JSContext *cx)
         if (fp->script && fp->pc) {
             report.filename = fp->script->filename;
             report.lineno = js_PCToLineNumber(cx, fp->script, fp->pc);
             break;
         }
     }
 
     /*
-     * If debugErrorHook is present then we give it a chance to veto
-     * sending the error on to the regular ErrorReporter.
+     * If debugErrorHook is present then we give it a chance to veto sending
+     * the error on to the regular ErrorReporter. We also clear a pending
+     * exception if any now so the hooks can replace the out-of-memory error
+     * by a script-catchable exception.
      */
+    cx->throwing = JS_FALSE;
     if (onError) {
         JSDebugErrorHook hook = cx->debugHooks->debugErrorHook;
         if (hook &&
             !hook(cx, msg, &report, cx->debugHooks->debugErrorHookData)) {
             onError = NULL;
         }
     }
 
--- a/js/src/jsemit.c
+++ b/js/src/jsemit.c
@@ -1320,67 +1320,22 @@ FlushPops(JSContext *cx, JSCodeGenerator
         return JS_FALSE;
     EMIT_UINT16_IMM_OP(JSOP_POPN, *npops);
     *npops = 0;
     return JS_TRUE;
 }
 
 /*
  * Emit additional bytecode(s) for non-local jumps.
- *
- * FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=379758
  */
 static JSBool
-EmitNonLocalJumpFixup(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *toStmt,
-                      JSOp *returnop)
+EmitNonLocalJumpFixup(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *toStmt)
 {
     intN depth, npops;
     JSStmtInfo *stmt;
-    ptrdiff_t jmp;
-
-    /*
-     * Return from a try block that has a finally clause or from a for-in loop
-     * must be split into two ops: JSOP_SETRVAL, to pop the r.v. and store it
-     * in fp->rval; and JSOP_RETRVAL, which makes control flow go back to the
-     * caller, who picks up fp->rval as usual.  Otherwise, the stack will be
-     * unbalanced when executing the finally clause.
-     *
-     * We mutate *returnop once only if we find an enclosing try-block (viz,
-     * STMT_FINALLY) or a for-in loop to ensure that we emit just one
-     * JSOP_SETRVAL before one or more JSOP_GOSUBs/JSOP_ENDITERs and other
-     * fixup opcodes emitted by this function.
-     *
-     * Our caller (the TOK_RETURN case of js_EmitTree) then emits *returnop.
-     * The fixup opcodes and gosubs/enditers must interleave in the proper
-     * order, from inner statement to outer, so that finally clauses run at
-     * the correct stack depth.
-     */
-    if (returnop) {
-        JS_ASSERT(*returnop == JSOP_RETURN);
-        for (stmt = cg->treeContext.topStmt; stmt != toStmt;
-             stmt = stmt->down) {
-            if (stmt->type == STMT_FINALLY ||
-                ((cg->treeContext.flags & TCF_FUN_HEAVYWEIGHT) &&
-                 STMT_MAYBE_SCOPE(stmt)) ||
-                stmt->type == STMT_FOR_IN_LOOP) {
-                if (js_Emit1(cx, cg, JSOP_SETRVAL) < 0)
-                    return JS_FALSE;
-                *returnop = JSOP_RETRVAL;
-                break;
-            }
-        }
-
-        /*
-         * If there are no try-with-finally blocks open around this return
-         * statement, we can generate a return forthwith and skip generating
-         * any fixup code.
-         */
-        if (*returnop == JSOP_RETURN)
-            return JS_TRUE;
-    }
 
     /*
      * The non-local jump fixup we emit will unbalance cg->stackDepth, because
      * the fixup replicates balanced code such as JSOP_LEAVEWITH emitted at the
      * end of a with statement, so we save cg->stackDepth here and restore it
      * just before a successful return.
      */
     depth = cg->stackDepth;
@@ -1389,18 +1344,17 @@ EmitNonLocalJumpFixup(JSContext *cx, JSC
 #define FLUSH_POPS() if (npops && !FlushPops(cx, cg, &npops)) return JS_FALSE
 
     for (stmt = cg->treeContext.topStmt; stmt != toStmt; stmt = stmt->down) {
         switch (stmt->type) {
           case STMT_FINALLY:
             FLUSH_POPS();
             if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
                 return JS_FALSE;
-            jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH, &GOSUBS(*stmt));
-            if (jmp < 0)
+            if (EmitBackPatchOp(cx, cg, JSOP_BACKPATCH, &GOSUBS(*stmt)) < 0)
                 return JS_FALSE;
             break;
 
           case STMT_WITH:
             /* There's a With object on the stack that we need to pop. */
             FLUSH_POPS();
             if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
                 return JS_FALSE;
@@ -1450,17 +1404,17 @@ EmitNonLocalJumpFixup(JSContext *cx, JSC
 }
 
 static ptrdiff_t
 EmitGoto(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *toStmt,
          ptrdiff_t *lastp, JSAtomListElement *label, JSSrcNoteType noteType)
 {
     intN index;
 
-    if (!EmitNonLocalJumpFixup(cx, cg, toStmt, NULL))
+    if (!EmitNonLocalJumpFixup(cx, cg, toStmt))
         return -1;
 
     if (label)
         index = js_NewSrcNote2(cx, cg, noteType, (ptrdiff_t) ALE_INDEX(label));
     else if (noteType != SRC_NULL)
         index = js_NewSrcNote(cx, cg, noteType);
     else
         index = 0;
@@ -5107,28 +5061,36 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
             if (!js_EmitTree(cx, cg, pn2))
                 return JS_FALSE;
         } else {
             if (js_Emit1(cx, cg, JSOP_PUSH) < 0)
                 return JS_FALSE;
         }
 
         /*
-         * EmitNonLocalJumpFixup mutates op to JSOP_RETRVAL after emitting a
-         * JSOP_SETRVAL if there are open try blocks having finally clauses.
+         * EmitNonLocalJumpFixup may add fixup bytecode to close open try
+         * blocks having finally clauses and to exit intermingled let blocks.
          * We can't simply transfer control flow to our caller in that case,
-         * because we must gosub to those clauses from inner to outer, with
-         * the correct stack pointer (i.e., after popping any with, for/in,
-         * etc., slots nested inside the finally's try).
+         * because we must gosub to those finally clauses from inner to outer,
+         * with the correct stack pointer (i.e., after popping any with,
+         * for/in, etc., slots nested inside the finally's try).
+         *
+         * In this case we mutate JSOP_RETURN into JSOP_SETRVAL and add an
+         * extra JSOP_RETRVAL after the fixups.
          */
-        op = JSOP_RETURN;
-        if (!EmitNonLocalJumpFixup(cx, cg, NULL, &op))
+        top = CG_OFFSET(cg);
+        if (js_Emit1(cx, cg, JSOP_RETURN) < 0)
+            return JS_FALSE;
+        if (!EmitNonLocalJumpFixup(cx, cg, NULL))
             return JS_FALSE;
-        if (js_Emit1(cx, cg, op) < 0)
-            return JS_FALSE;
+        if (top + JSOP_RETURN_LENGTH != CG_OFFSET(cg)) {
+            CG_BASE(cg)[top] = JSOP_SETRVAL;
+            if (js_Emit1(cx, cg, JSOP_RETRVAL) < 0)
+                return JS_FALSE;
+        }
         break;
 
 #if JS_HAS_GENERATORS
       case TOK_YIELD:
         if (!(cg->treeContext.flags & TCF_IN_FUNCTION)) {
             js_ReportCompileErrorNumber(cx, CG_TS(cg), pn, JSREPORT_ERROR,
                                         JSMSG_BAD_RETURN_OR_YIELD,
                                         js_yield_str);
--- a/js/src/jsinterp.c
+++ b/js/src/jsinterp.c
@@ -673,18 +673,18 @@ AllocateAfterSP(JSContext *cx, jsval *sp
         return JS_FALSE;
 
     JS_ARENA_ALLOCATE_CAST(sp2, jsval *, &cx->stackPool,
                            (nslots - surplus) * sizeof(jsval));
     JS_ASSERT(sp2 == sp + surplus);
     return JS_TRUE;
 }
 
-JS_FRIEND_API(jsval *)
-js_AllocRawStack(JSContext *cx, uintN nslots, void **markp)
+static jsval *
+AllocRawStack(JSContext *cx, uintN nslots, void **markp)
 {
     jsval *sp;
 
     if (!cx->stackPool.first.next) {
         int64 *timestamp;
 
         JS_ARENA_ALLOCATE(timestamp, &cx->stackPool, sizeof *timestamp);
         if (!timestamp) {
@@ -697,18 +697,18 @@ js_AllocRawStack(JSContext *cx, uintN ns
     if (markp)
         *markp = JS_ARENA_MARK(&cx->stackPool);
     JS_ARENA_ALLOCATE_CAST(sp, jsval *, &cx->stackPool, nslots * sizeof(jsval));
     if (!sp)
         js_ReportOutOfScriptQuota(cx);
     return sp;
 }
 
-JS_FRIEND_API(void)
-js_FreeRawStack(JSContext *cx, void *mark)
+static void
+FreeRawStack(JSContext *cx, void *mark)
 {
     JS_ARENA_RELEASE(&cx->stackPool, mark);
 }
 
 JS_FRIEND_API(jsval *)
 js_AllocStack(JSContext *cx, uintN nslots, void **markp)
 {
     jsval *sp;
@@ -717,17 +717,17 @@ js_AllocStack(JSContext *cx, uintN nslot
 
     /* Callers don't check for zero nslots: we do to avoid empty segments. */
     if (nslots == 0) {
         *markp = NULL;
         return (jsval *) JS_ARENA_MARK(&cx->stackPool);
     }
 
     /* Allocate 2 extra slots for the stack segment header we'll likely need. */
-    sp = js_AllocRawStack(cx, 2 + nslots, markp);
+    sp = AllocRawStack(cx, 2 + nslots, markp);
     if (!sp)
         return NULL;
 
     /* Try to avoid another header if we can piggyback on the last segment. */
     a = cx->stackPool.current;
     sh = cx->stackHeaders;
     if (sh && JS_STACK_SEGMENT(sh) + sh->nslots == sp) {
         /* Extend the last stack segment, give back the 2 header slots. */
@@ -859,37 +859,16 @@ js_GetScopeChain(JSContext *cx, JSStackF
         cursor = parent;
     }
     fp->flags |= JSFRAME_POP_BLOCKS;
     fp->scopeChain = obj;
     fp->blockChain = NULL;
     return obj;
 }
 
-/*
- * Walk the scope chain looking for block scopes whose locals need to be
- * copied from stack slots into object slots before fp goes away.
- */
-static JSBool
-PutBlockObjects(JSContext *cx, JSStackFrame *fp)
-{
-    JSBool ok;
-    JSObject *obj;
-
-    ok = JS_TRUE;
-    for (obj = fp->scopeChain; obj; obj = OBJ_GET_PARENT(cx, obj)) {
-        if (OBJ_GET_CLASS(cx, obj) == &js_BlockClass) {
-            if (OBJ_GET_PRIVATE(cx, obj) != fp)
-                break;
-            ok &= js_PutBlockObject(cx, obj);
-        }
-    }
-    return ok;
-}
-
 JSBool
 js_GetPrimitiveThis(JSContext *cx, jsval *vp, JSClass *clasp, jsval *thisvp)
 {
     jsval v;
     JSObject *obj;
 
     v = vp[1];
     if (JSVAL_IS_OBJECT(v)) {
@@ -1260,17 +1239,17 @@ have_fun:
         /*
          * The extra slots required by the function must be continues with the
          * arguments. Thus, when the last arena does not have room to fit
          * nslots right after sp and AllocateAfterSP fails, we have to copy
          * [vp..vp+2+argc) slots and clear rootedArgsFlag to root the copy.
          */
         if (!AllocateAfterSP(cx, sp, nslots)) {
             rootedArgsFlag = 0;
-            newvp = js_AllocRawStack(cx, 2 + argc + nslots, NULL);
+            newvp = AllocRawStack(cx, 2 + argc + nslots, NULL);
             if (!newvp) {
                 ok = JS_FALSE;
                 goto out2;
             }
             memcpy(newvp, vp, (2 + argc) * sizeof(jsval));
             argv = newvp + 2;
             sp = argv + argc;
         }
@@ -1310,17 +1289,17 @@ have_fun:
 #endif
         goto out2;
     }
 
     /* Now allocate stack space for local variables of interpreted function. */
     if (nvars) {
         if (!AllocateAfterSP(cx, sp, nvars)) {
             /* NB: Discontinuity between argv and vars. */
-            sp = js_AllocRawStack(cx, nvars, NULL);
+            sp = AllocRawStack(cx, nvars, NULL);
             if (!sp) {
                 ok = JS_FALSE;
                 goto out2;
             }
         }
 
         /* Push void to initialize local variables. */
         i = nvars;
@@ -1355,19 +1334,16 @@ have_fun:
     frame.pc = NULL;
     frame.spbase = NULL;
     frame.sharpDepth = 0;
     frame.sharpArray = NULL;
     frame.flags = flags | rootedArgsFlag;
     frame.dormantNext = NULL;
     frame.xmlNamespace = NULL;
     frame.blockChain = NULL;
-#ifdef DEBUG
-    frame.pcDisabledSave = JS_PROPERTY_CACHE(cx).disabled;
-#endif
 
     /* From here on, control must flow through label out: to return. */
     cx->fp = &frame;
 
     /* Init these now in case we goto out before first hook call. */
     hook = cx->debugHooks->callHook;
     hookData = NULL;
 
@@ -1579,17 +1555,17 @@ js_Execute(JSContext *cx, JSObject *chai
         frame.fun = NULL;
         frame.thisp = chain;
         frame.argc = 0;
         frame.argv = NULL;
         frame.nvars = script->ngvars;
         if (script->regexpsOffset != 0)
             frame.nvars += JS_SCRIPT_REGEXPS(script)->length;
         if (frame.nvars != 0) {
-            frame.vars = js_AllocRawStack(cx, frame.nvars, &mark);
+            frame.vars = AllocRawStack(cx, frame.nvars, &mark);
             if (!frame.vars) {
                 ok = JS_FALSE;
                 goto out;
             }
             memset(frame.vars, 0, frame.nvars * sizeof(jsval));
         } else {
             frame.vars = NULL;
         }
@@ -1602,19 +1578,16 @@ js_Execute(JSContext *cx, JSObject *chai
     frame.pc = NULL;
     frame.sp = oldfp ? oldfp->sp : NULL;
     frame.spbase = NULL;
     frame.sharpDepth = 0;
     frame.flags = flags;
     frame.dormantNext = NULL;
     frame.xmlNamespace = NULL;
     frame.blockChain = NULL;
-#ifdef DEBUG
-    frame.pcDisabledSave = JS_PROPERTY_CACHE(cx).disabled;
-#endif
 
     /*
      * Here we wrap the call to js_Interpret with code to (conditionally)
      * save and restore the old stack frame chain into a chain of 'dormant'
      * frame chains.  Since we are replacing cx->fp, we were running into
      * the problem that if GC was called under this frame, some of the GC
      * things associated with the old frame chain (available here only in
      * the C variable 'oldfp') were not rooted and were being collected.
@@ -1644,17 +1617,17 @@ js_Execute(JSContext *cx, JSObject *chai
     *result = frame.rval;
 
     if (hookData) {
         hook = cx->debugHooks->executeHook;
         if (hook)
             hook(cx, &frame, JS_FALSE, &ok, hookData);
     }
     if (mark)
-        js_FreeRawStack(cx, mark);
+        FreeRawStack(cx, mark);
     cx->fp = oldfp;
 
     if (oldfp && oldfp != down) {
         JS_ASSERT(cx->dormantFrameChain == oldfp);
         cx->dormantFrameChain = oldfp->dormantNext;
         oldfp->dormantNext = NULL;
     }
 
@@ -2073,21 +2046,93 @@ EnterWith(JSContext *cx, jsint stackInde
 
 static void
 LeaveWith(JSContext *cx)
 {
     JSObject *withobj;
 
     withobj = cx->fp->scopeChain;
     JS_ASSERT(OBJ_GET_CLASS(cx, withobj) == &js_WithClass);
+    JS_ASSERT(OBJ_GET_PRIVATE(cx, withobj) == cx->fp);
+    JS_ASSERT(OBJ_BLOCK_DEPTH(cx, withobj) >= 0);
     cx->fp->scopeChain = OBJ_GET_PARENT(cx, withobj);
     JS_SetPrivate(cx, withobj, NULL);
     js_EnablePropertyCache(cx);
 }
 
+static JSClass *
+IsActiveWithOrBlock(JSContext *cx, JSObject *obj, int stackDepth)
+{
+    JSClass *clasp;
+
+    clasp = OBJ_GET_CLASS(cx, obj);
+    if ((clasp == &js_WithClass || clasp == &js_BlockClass) &&
+        OBJ_GET_PRIVATE(cx, obj) == cx->fp &&
+        OBJ_BLOCK_DEPTH(cx, obj) >= stackDepth) {
+        return clasp;
+    }
+    return NULL;
+}
+
+static jsint
+CountWithBlocks(JSContext *cx, JSStackFrame *fp)
+{
+    jsint n;
+    JSObject *obj;
+    JSClass *clasp;
+
+    n = 0;
+    for (obj = fp->scopeChain;
+         (clasp = IsActiveWithOrBlock(cx, obj, 0)) != NULL;
+         obj = OBJ_GET_PARENT(cx, obj)) {
+        if (clasp == &js_WithClass)
+            ++n;
+    }
+    return n;
+}
+
+/*
+ * Unwind block and scope chains to match the given depth. The function sets
+ * fp->sp on return to stackDepth.
+ */
+static JSBool
+UnwindScope(JSContext *cx, JSStackFrame *fp, jsint stackDepth,
+            JSBool normalUnwind)
+{
+    JSObject *obj;
+    JSClass *clasp;
+
+    JS_ASSERT(stackDepth >= 0);
+    JS_ASSERT(fp->spbase + stackDepth <= fp->sp);
+
+    for (obj = fp->blockChain; obj; obj = OBJ_GET_PARENT(cx, obj)) {
+        JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_BlockClass);
+        if (OBJ_BLOCK_DEPTH(cx, obj) < stackDepth)
+            break;
+    }
+    fp->blockChain = obj;
+
+    for (;;) {
+        obj = fp->scopeChain;
+        clasp = IsActiveWithOrBlock(cx, obj, stackDepth);
+        if (!clasp)
+            break;
+        if (clasp == &js_BlockClass) {
+            /* Don't fail until after we've updated all stacks. */
+            normalUnwind &= js_PutBlockObject(cx, obj, normalUnwind);
+            fp->scopeChain = OBJ_GET_PARENT(cx, obj);
+        } else {
+            LeaveWith(cx);
+        }
+    }
+
+    fp->sp = fp->spbase + stackDepth;
+    return normalUnwind;
+}
+
 /*
  * Threaded interpretation via computed goto appears to be well-supported by
  * GCC 3 and higher.  IBM's C compiler when run with the right options (e.g.,
  * -qlanglvl=extended) also supports threading.  Ditto the SunPro C compiler.
  * Currently it's broken for JS_VERSION < 160, though this isn't worth fixing.
  * Add your compiler support macros here.
  */
 #ifndef JS_THREADED_INTERP
@@ -2301,17 +2346,16 @@ js_Interpret(JSContext *cx, jsbytecode *
 #if JS_HAS_EXPORT_IMPORT
     JSIdArray *ida;
 #endif
     jsint low, high, off, npairs;
     JSBool match;
 #if JS_HAS_GETTER_SETTER
     JSPropertyOp getter, setter;
 #endif
-    int stackDummy;
 
 #ifdef __GNUC__
 # define JS_EXTENSION __extension__
 # define JS_EXTENSION_(s) __extension__ ({ s; })
 #else
 # define JS_EXTENSION
 # define JS_EXTENSION_(s) s
 #endif
@@ -2347,16 +2391,19 @@ js_Interpret(JSContext *cx, jsbytecode *
 # define DO_OP()            goto do_op
 # define DO_NEXT_OP(n)      goto advance_pc
 # define BEGIN_CASE(OP)     case OP:
 # define END_CASE(OP)       break;
 # define END_VARLEN_CASE    break;
 # define EMPTY_CASE(OP)     BEGIN_CASE(OP) END_CASE(OP)
 #endif
 
+    /* Check for too deep a C stack. */
+    JS_CHECK_RECURSION(cx, return JS_FALSE);
+
     *result = JSVAL_VOID;
     rt = cx->runtime;
 
     /* Set registerized frame pointer and derived script pointer. */
     fp = cx->fp;
     script = fp->script;
     JS_ASSERT(script->length != 0);
 
@@ -2387,36 +2434,16 @@ js_Interpret(JSContext *cx, jsbytecode *
 
 #define LOAD_FUNCTION(PCOFF)                                                  \
     JS_BEGIN_MACRO                                                            \
         LOAD_OBJECT(PCOFF);                                                   \
         JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_FunctionClass);               \
     JS_END_MACRO
 
     /*
-     * Optimized Get and SetVersion for proper script language versioning.
-     *
-     * If any native method or JSClass/JSObjectOps hook calls js_SetVersion
-     * and changes cx->version, the effect will "stick" and we will stop
-     * maintaining currentVersion.  This is relied upon by testsuites, for
-     * the most part -- web browsers select version before compiling and not
-     * at run-time.
-     */
-    currentVersion = (JSVersion) script->version;
-    originalVersion = (JSVersion) cx->version;
-    if (currentVersion != originalVersion)
-        js_SetVersion(cx, currentVersion);
-
-#ifdef __GNUC__
-    flags = 0;  /* suppress gcc warnings */
-    id = 0;
-    atom = NULL;
-#endif
-
-    /*
      * Prepare to call a user-supplied branch handler, and abort the script
      * if it returns false.
      */
 #define CHECK_BRANCH(len)                                                     \
     JS_BEGIN_MACRO                                                            \
         if (len <= 0 && (cx->operationCount -= JSOW_SCRIPT_JUMP) <= 0) {      \
             SAVE_SP_AND_PC(fp);                                               \
             ok = js_ResetOperationCount(cx);                                  \
@@ -2441,61 +2468,79 @@ js_Interpret(JSContext *cx, jsbytecode *
 #define LOAD_INTERRUPT_HANDLER(cx)                                            \
     JS_BEGIN_MACRO                                                            \
         interruptHandler = (cx)->debugHooks->interruptHandler;                \
         LOAD_JUMP_TABLE();                                                    \
     JS_END_MACRO
 
     LOAD_INTERRUPT_HANDLER(cx);
 
-    /* Check for too much js_Interpret nesting, or too deep a C stack. */
+    /*
+     * Optimized Get and SetVersion for proper script language versioning.
+     *
+     * If any native method or JSClass/JSObjectOps hook calls js_SetVersion
+     * and changes cx->version, the effect will "stick" and we will stop
+     * maintaining currentVersion.  This is relied upon by testsuites, for
+     * the most part -- web browsers select version before compiling and not
+     * at run-time.
+     */
+    currentVersion = (JSVersion) script->version;
+    originalVersion = (JSVersion) cx->version;
+    if (currentVersion != originalVersion)
+        js_SetVersion(cx, currentVersion);
+
     ++cx->interpLevel;
-    if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) {
-        js_ReportOverRecursed(cx);
-        ok = JS_FALSE;
-        goto out2;
-    }
+#ifdef DEBUG
+    fp->pcDisabledSave = JS_PROPERTY_CACHE(cx).disabled;
+#endif
+
+    /* From this point the control must flow through the label exit. */
+    ok = JS_TRUE;
 
     /*
      * Allocate operand and pc stack slots for the script's worst-case depth,
      * unless we're called to interpret a part of an already active script, a
      * filtering predicate expression for example.
      */
     depth = (jsint) script->depth;
     if (JS_LIKELY(!fp->spbase)) {
-        newsp = js_AllocRawStack(cx, (uintN)(2 * depth), &mark);
+        ASSERT_NOT_THROWING(cx);
+        newsp = AllocRawStack(cx, (uintN)(2 * depth), &mark);
         if (!newsp) {
+            mark = NULL;
             ok = JS_FALSE;
-            goto out2;
+            goto exit;
         }
+        JS_ASSERT(mark);
         sp = newsp + depth;
         fp->spbase = sp;
         SAVE_SP(fp);
     } else {
+        JS_ASSERT(fp->flags & JSFRAME_GENERATOR);
         sp = fp->sp;
         JS_ASSERT(JS_UPTRDIFF(sp, fp->spbase) <= depth * sizeof(jsval));
         newsp = fp->spbase - depth;
         mark = NULL;
-    }
-
-    /*
-     * To support generator_throw and to catch ignored exceptions, fail right
-     * away if cx->throwing is set.  If no exception is pending, null obj in
-     * case a callable object is being sent into a yield expression, and the
-     * yield's result is invoked.
-     */
-    ok = !cx->throwing;
-    if (!ok) {
+        JS_ASSERT(JS_PROPERTY_CACHE(cx).disabled >= 0);
+        JS_PROPERTY_CACHE(cx).disabled += CountWithBlocks(cx, fp);
+
+        /*
+         * To support generator_throw and to catch ignored exceptions,
+         * fail if cx->throwing is set.
+         */
+        if (cx->throwing) {
 #ifdef DEBUG_NOT_THROWING
-        if (cx->exception != JSVAL_ARETURN) {
-            printf("JS INTERPRETER CALLED WITH PENDING EXCEPTION %lx\n",
-                   (unsigned long) cx->exception);
+            if (cx->exception != JSVAL_ARETURN) {
+                printf("JS INTERPRETER CALLED WITH PENDING EXCEPTION %lx\n",
+                       (unsigned long) cx->exception);
+            }
+#endif
+            ok = JS_FALSE;
+            goto out;
         }
-#endif
-        goto out;
     }
 
 #if JS_THREADED_INTERP
 
     /*
      * This is a loop, but it does not look like a loop.  The loop-closing
      * jump is distributed throughout interruptJumpTable, and comes back to
      * the interrupt label.  The dispatch on op is through normalJumpTable.
@@ -2515,17 +2560,17 @@ interrupt:
                                  cx->debugHooks->interruptHandlerData)) {
           case JSTRAP_ERROR:
             ok = JS_FALSE;
             goto out;
           case JSTRAP_CONTINUE:
             break;
           case JSTRAP_RETURN:
             fp->rval = rval;
-            goto out;
+            goto forced_return;
           case JSTRAP_THROW:
             cx->throwing = JS_TRUE;
             cx->exception = rval;
             ok = JS_FALSE;
             goto out;
           default:;
         }
         LOAD_INTERRUPT_HANDLER(cx);
@@ -2574,34 +2619,31 @@ interrupt:
                                      cx->debugHooks->interruptHandlerData)) {
               case JSTRAP_ERROR:
                 ok = JS_FALSE;
                 goto out;
               case JSTRAP_CONTINUE:
                 break;
               case JSTRAP_RETURN:
                 fp->rval = rval;
-                goto out;
+                goto forced_return;
               case JSTRAP_THROW:
                 cx->throwing = JS_TRUE;
                 cx->exception = rval;
                 ok = JS_FALSE;
                 goto out;
               default:;
             }
             LOAD_INTERRUPT_HANDLER(cx);
         }
 
         switch (op) {
 
 #endif /* !JS_THREADED_INTERP */
 
-          BEGIN_CASE(JSOP_STOP)
-            goto out;
-
           EMPTY_CASE(JSOP_NOP)
 
           EMPTY_CASE(JSOP_GROUP)
 
           BEGIN_CASE(JSOP_PUSH)
             PUSH_OPND(JSVAL_VOID);
           END_CASE(JSOP_PUSH)
 
@@ -2678,34 +2720,27 @@ interrupt:
           END_CASE(JSOP_SETRVAL)
 
           BEGIN_CASE(JSOP_RETURN)
             CHECK_BRANCH(-1);
             fp->rval = POP_OPND();
             /* FALL THROUGH */
 
           BEGIN_CASE(JSOP_RETRVAL)    /* fp->rval already set */
+          BEGIN_CASE(JSOP_STOP)
             ASSERT_NOT_THROWING(cx);
             if (inlineCallCount)
           inline_return:
             {
                 JSInlineFrame *ifp = (JSInlineFrame *) fp;
                 void *hookData = ifp->hookData;
 
-                /*
-                 * If fp has blocks on its scope chain, home their locals now,
-                 * before calling any debugger hook, and before freeing stack.
-                 * This matches the order of block putting and hook calling in
-                 * the "out-of-line" return code at the bottom of js_Interpret
-                 * and in js_Invoke.
-                 */
-                if (fp->flags & JSFRAME_POP_BLOCKS) {
-                    SAVE_SP_AND_PC(fp);
-                    ok &= PutBlockObjects(cx, fp);
-                }
+                JS_ASSERT(JS_PROPERTY_CACHE(cx).disabled == fp->pcDisabledSave);
+                JS_ASSERT(!fp->blockChain);
+                JS_ASSERT(!IsActiveWithOrBlock(cx, fp->scopeChain, 0));
 
                 if (hookData) {
                     JSInterpreterHook hook = cx->debugHooks->callHook;
                     if (hook) {
                         SAVE_SP_AND_PC(fp);
                         hook(cx, fp, JS_FALSE, &ok, hookData);
                         LOAD_INTERRUPT_HANDLER(cx);
                     }
@@ -2765,18 +2800,19 @@ interrupt:
 
                 /* Resume execution in the calling frame. */
                 inlineCallCount--;
                 if (JS_LIKELY(ok)) {
                     JS_ASSERT(js_CodeSpec[*pc].length == JSOP_CALL_LENGTH);
                     len = JSOP_CALL_LENGTH;
                     DO_NEXT_OP(len);
                 }
+                goto out;
             }
-            goto out;
+            goto exit;
 
           BEGIN_CASE(JSOP_DEFAULT)
             (void) POP();
             /* FALL THROUGH */
           BEGIN_CASE(JSOP_GOTO)
             len = GET_JUMP_OFFSET(pc);
             CHECK_BRANCH(len);
           END_VARLEN_CASE
@@ -4451,17 +4487,18 @@ interrupt:
                     newifp->frame.scopeChain = parent = OBJ_GET_PARENT(cx, obj);
                     newifp->frame.sharpDepth = 0;
                     newifp->frame.sharpArray = NULL;
                     newifp->frame.flags = 0;
                     newifp->frame.dormantNext = NULL;
                     newifp->frame.xmlNamespace = NULL;
                     newifp->frame.blockChain = NULL;
 #ifdef DEBUG
-                    newifp->frame.pcDisabledSave = JS_PROPERTY_CACHE(cx).disabled;
+                    newifp->frame.pcDisabledSave =
+                        JS_PROPERTY_CACHE(cx).disabled;
 #endif
                     newifp->rvp = rvp;
                     newifp->mark = newmark;
 
                     /* Compute the 'this' parameter now that argv is set. */
                     JS_ASSERT(!JSFUN_BOUND_METHOD_TEST(fun->flags));
                     JS_ASSERT(!JSVAL_IS_PRIMITIVE(vp[1]));
                     newifp->frame.thisp = (JSObject *)vp[1];
@@ -4520,17 +4557,17 @@ interrupt:
                     DO_OP();
 
                   bad_inline_call:
                     RESTORE_SP(fp);
                     JS_ASSERT(fp->pc == pc);
                     script = fp->script;
                     depth = (jsint) script->depth;
                     atoms = script->atomMap.vector;
-                    js_FreeRawStack(cx, newmark);
+                    FreeRawStack(cx, newmark);
                     ok = JS_FALSE;
                     goto out;
                 }
 
 #ifdef INCLUDE_MOZILLA_DTRACE
                 /* DTrace function entry, non-inlines */
                 if (VALUE_IS_FUNCTION(cx, lval)) {
                     if (JAVASCRIPT_FUNCTION_ENTRY_ENABLED())
@@ -5148,17 +5185,17 @@ interrupt:
               case JSTRAP_CONTINUE:
                 JS_ASSERT(JSVAL_IS_INT(rval));
                 op = (JSOp) JSVAL_TO_INT(rval);
                 JS_ASSERT((uintN)op < (uintN)JSOP_LIMIT);
                 LOAD_INTERRUPT_HANDLER(cx);
                 DO_OP();
               case JSTRAP_RETURN:
                 fp->rval = rval;
-                goto out;
+                goto forced_return;
               case JSTRAP_THROW:
                 cx->throwing = JS_TRUE;
                 cx->exception = rval;
                 ok = JS_FALSE;
                 goto out;
               default:;
             }
             LOAD_INTERRUPT_HANDLER(cx);
@@ -5963,17 +6000,17 @@ interrupt:
                                 cx->debugHooks->debuggerHandlerData)) {
                   case JSTRAP_ERROR:
                     ok = JS_FALSE;
                     goto out;
                   case JSTRAP_CONTINUE:
                     break;
                   case JSTRAP_RETURN:
                     fp->rval = rval;
-                    goto out;
+                    goto forced_return;
                   case JSTRAP_THROW:
                     cx->throwing = JS_TRUE;
                     cx->exception = rval;
                     ok = JS_FALSE;
                     goto out;
                   default:;
                 }
                 LOAD_INTERRUPT_HANDLER(cx);
@@ -6329,17 +6366,17 @@ interrupt:
                 chainp = &fp->scopeChain;
                 obj = *chainp;
 
                 /*
                  * This block was cloned, so clear its private data and sync
                  * its locals to their property slots.
                  */
                 SAVE_SP_AND_PC(fp);
-                ok = js_PutBlockObject(cx, obj);
+                ok = js_PutBlockObject(cx, obj, JS_TRUE);
                 if (!ok)
                     goto out;
             }
 
             sp -= GET_UINT16(pc);
             JS_ASSERT(fp->spbase <= sp && sp <= fp->spbase + depth);
 
             /* Store the result into the topmost stack slot. */
@@ -6347,19 +6384,18 @@ interrupt:
                 STORE_OPND(-1, rval);
 
             JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_BlockClass);
             JS_ASSERT(op == JSOP_LEAVEBLOCKEXPR
                       ? fp->spbase + OBJ_BLOCK_DEPTH(cx, obj) == sp - 1
                       : fp->spbase + OBJ_BLOCK_DEPTH(cx, obj) == sp);
 
             *chainp = OBJ_GET_PARENT(cx, obj);
-            JS_ASSERT(chainp != &fp->blockChain ||
-                      !*chainp ||
-                      OBJ_GET_CLASS(cx, *chainp) == &js_BlockClass);
+            JS_ASSERT_IF(fp->blockChain,
+                         OBJ_GET_CLASS(cx, fp->blockChain) == &js_BlockClass);
           }
           END_CASE(JSOP_LEAVEBLOCK)
 
           BEGIN_CASE(JSOP_GETLOCAL)
           BEGIN_CASE(JSOP_CALLLOCAL)
             slot = GET_UINT16(pc);
             JS_ASSERT(slot < (uintN)depth);
             PUSH_OPND(fp->spbase[slot]);
@@ -6420,36 +6456,40 @@ interrupt:
 
 #if JS_HAS_GENERATORS
           BEGIN_CASE(JSOP_GENERATOR)
             pc += JSOP_GENERATOR_LENGTH;
             SAVE_SP_AND_PC(fp);
             obj = js_NewGenerator(cx, fp);
             if (!obj) {
                 ok = JS_FALSE;
-            } else {
-                JS_ASSERT(!fp->callobj && !fp->argsobj);
-                fp->rval = OBJECT_TO_JSVAL(obj);
+                goto out;
             }
-            goto out;
+            JS_ASSERT(!fp->callobj && !fp->argsobj);
+            fp->rval = OBJECT_TO_JSVAL(obj);
+            if (inlineCallCount != 0)
+                goto inline_return;
+            goto exit;
 
           BEGIN_CASE(JSOP_YIELD)
             ASSERT_NOT_THROWING(cx);
             if (FRAME_TO_GENERATOR(fp)->state == JSGEN_CLOSING) {
                 SAVE_SP_AND_PC(fp);
                 js_ReportValueError(cx, JSMSG_BAD_GENERATOR_YIELD,
                                     JSDVG_SEARCH_STACK, fp->argv[-2], NULL);
                 ok = JS_FALSE;
                 goto out;
             }
             fp->rval = FETCH_OPND(-1);
             fp->flags |= JSFRAME_YIELDING;
             pc += JSOP_YIELD_LENGTH;
             SAVE_SP_AND_PC(fp);
-            goto out;
+            JS_PROPERTY_CACHE(cx).disabled -= CountWithBlocks(cx, fp);
+            JS_ASSERT(JS_PROPERTY_CACHE(cx).disabled >= 0);
+            goto exit;
 
           BEGIN_CASE(JSOP_ARRAYPUSH)
             slot = GET_UINT16(pc);
             JS_ASSERT(slot < (uintN)depth);
             lval = fp->spbase[slot];
             obj  = JSVAL_TO_OBJECT(lval);
             JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_ArrayClass);
             rval = FETCH_OPND(-1);
@@ -6575,40 +6615,38 @@ interrupt:
                 fputc(' ', tracefp);
             }
             fputc('\n', tracefp);
         }
 #endif /* DEBUG */
     }
 #endif /* !JS_THREADED_INTERP */
 
-out:
+  out:
     JS_ASSERT((size_t)(pc - script->code) < script->length);
     if (!ok && cx->throwing) {
         /* An exception has been raised. */
         JSTrapHandler handler;
         JSTryNote *tn, *tnlimit;
         uint32 offset;
 
-        /*
-         * Call debugger throw hook if set (XXX thread safety?).
-         */
+        /* Call debugger throw hook if set. */
         handler = cx->debugHooks->throwHook;
         if (handler) {
             SAVE_SP_AND_PC(fp);
             switch (handler(cx, script, pc, &rval,
                             cx->debugHooks->throwHookData)) {
               case JSTRAP_ERROR:
                 cx->throwing = JS_FALSE;
-                goto no_catch;
+                goto forced_return;
               case JSTRAP_RETURN:
-                ok = JS_TRUE;
                 cx->throwing = JS_FALSE;
                 fp->rval = rval;
-                goto no_catch;
+                ok = JS_TRUE;
+                goto forced_return;
               case JSTRAP_THROW:
                 cx->exception = rval;
               case JSTRAP_CONTINUE:
               default:;
             }
             LOAD_INTERRUPT_HANDLER(cx);
         }
 
@@ -6643,64 +6681,33 @@ out:
              * executed [enditer] and [gosub] opcodes will have try notes
              * with the stack depth exceeding the current one and this
              * condition is what we use to filter them out.
              */
             if (tn->stackDepth > sp - fp->spbase)
                 continue;
 
             /*
-             * Prepare to execute the try note handler and unwind the block
-             * and scope chains until we match the stack depth of the try
-             * note. Note that we set sp after we call js_PutBlockObject to
-             * avoid potential GC hazards.
-             */
-            ok = JS_TRUE;
-            i = tn->stackDepth;
-            for (obj = fp->blockChain; obj; obj = OBJ_GET_PARENT(cx, obj)) {
-                JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_BlockClass);
-                if (OBJ_BLOCK_DEPTH(cx, obj) < i)
-                    break;
-            }
-            fp->blockChain = obj;
-
-            JS_ASSERT(ok);
-            for (;;) {
-                obj = fp->scopeChain;
-                clasp = OBJ_GET_CLASS(cx, obj);
-                if (clasp != &js_WithClass && clasp != &js_BlockClass)
-                    break;
-                if (OBJ_GET_PRIVATE(cx, obj) != fp ||
-                    OBJ_BLOCK_DEPTH(cx, obj) < i) {
-                    break;
-                }
-                if (clasp == &js_BlockClass) {
-                    /* Don't fail until after we've updated all stacks. */
-                    ok &= js_PutBlockObject(cx, obj);
-                    fp->scopeChain = OBJ_GET_PARENT(cx, obj);
-                } else {
-                    LeaveWith(cx);
-                }
-            }
-
-            sp = fp->spbase + i;
-
-            /*
              * Set pc to the first bytecode after the the try note to point
              * to the beginning of catch or finally or to [enditer] closing
              * the for-in loop.
-             *
-             * We do it before checking for ok so, when failing during the
-             * scope recovery, we restart the exception search with the
-             * updated stack and pc avoiding calling the handler again.
              */
-            offset = tn->start + tn->length;
-            pc = (script)->main + offset;
-            if (!ok)
+            pc = (script)->main + tn->start + tn->length;
+
+            SAVE_SP_AND_PC(fp);
+            ok = UnwindScope(cx, fp, tn->stackDepth, JS_TRUE);
+            JS_ASSERT(fp->sp == fp->spbase + tn->stackDepth);
+            sp = fp->sp;
+            if (!ok) {
+                /*
+                 * Restart the handler search with updated pc and stack depth
+                 * to properly notify the debugger.
+                 */
                 goto out;
+            }
 
             switch (tn->kind) {
               case JSTN_CATCH:
                 JS_ASSERT(*pc == JSOP_ENTERBLOCK);
 
 #if JS_HAS_GENERATORS
                 /* Catch cannot intercept the closing of a generator. */
                 if (JS_UNLIKELY(cx->exception == JSVAL_ARETURN))
@@ -6764,71 +6771,66 @@ out:
         if (JS_UNLIKELY(cx->throwing && cx->exception == JSVAL_ARETURN)) {
             cx->throwing = JS_FALSE;
             ok = JS_TRUE;
             fp->rval = JSVAL_VOID;
         }
 #endif
     }
 
+  forced_return:
+    /*
+     * We either have an error or uncaught exception or were forced to return
+     * from a trap handler.
+     */
+    SAVE_SP_AND_PC(fp);
+
+    /* ok must stay false even when UnwindScope returns true. */
+    ok &= UnwindScope(cx, fp, 0, ok || cx->throwing);
+    JS_ASSERT(fp->sp == fp->spbase);
+    sp = fp->sp;
+
+    if (inlineCallCount)
+        goto inline_return;
+
+  exit:
     /*
      * At this point we are inevitably leaving an interpreted function or a
      * top-level script, and returning to one of:
-     * (a) an inline call in the same js_Interpret;
-     * (b) an "out of line" call made through js_Invoke;
-     * (c) a js_Execute activation;
-     * (d) a generator (SendToGenerator, jsiter.c).
+     * (a) an "out of line" call made through js_Invoke;
+     * (b) a js_Execute activation;
+     * (c) a generator (SendToGenerator, jsiter.c).
+     *
+     * We must not be in an inline frame since the check above ensures that
+     * for the error case and for a normal return the code jumps directly to
+     * parent's frame pc.
      */
-    if (!ok) {
-        for (obj = fp->scopeChain; obj; obj = OBJ_GET_PARENT(cx, obj)) {
-            clasp = OBJ_GET_CLASS(cx, obj);
-            if (clasp != &js_WithClass && clasp != &js_BlockClass)
-                break;
-            if (OBJ_GET_PRIVATE(cx, obj) != fp || OBJ_BLOCK_DEPTH(cx, obj) < 0)
-                break;
-            if (clasp == &js_WithClass)
-                js_EnablePropertyCache(cx);
-        }
-    }
-    JS_ASSERT_IF(mark, JS_PROPERTY_CACHE(cx).disabled == fp->pcDisabledSave);
-
-    /*
-     * Check whether control fell off the end of a lightweight function, or an
-     * exception thrown under such a function was not caught by it.  If so, go
-     * to the inline code under JSOP_RETURN.
-     */
-    if (inlineCallCount)
-        goto inline_return;
+    JS_ASSERT(inlineCallCount == 0);
+    JS_ASSERT(JS_PROPERTY_CACHE(cx).disabled == fp->pcDisabledSave);
 
     /*
      * Reset sp before freeing stack slots, because our caller may GC soon.
      * Clear spbase to indicate that we've popped the 2 * depth operand slots.
      * Restore the previous frame's execution state.
      */
     if (JS_LIKELY(mark != NULL)) {
-        /* If fp has blocks on its scope chain, home their locals now. */
-        if (fp->flags & JSFRAME_POP_BLOCKS) {
-            SAVE_SP_AND_PC(fp);
-            ok &= PutBlockObjects(cx, fp);
-        }
-
+        JS_ASSERT(!fp->blockChain);
+        JS_ASSERT(!IsActiveWithOrBlock(cx, fp->scopeChain, 0));
+        JS_ASSERT(!(fp->flags & JSFRAME_GENERATOR));
         fp->sp = fp->spbase;
         fp->spbase = NULL;
-        js_FreeRawStack(cx, mark);
-    } else {
-        SAVE_SP(fp);
+        FreeRawStack(cx, mark);
     }
 
-out2:
     if (cx->version == currentVersion && currentVersion != originalVersion)
         js_SetVersion(cx, originalVersion);
     cx->interpLevel--;
     return ok;
 
-atom_not_defined:
+  atom_not_defined:
     {
         const char *printable;
 
         ASSERT_SAVED_SP_AND_PC(fp);
         printable = js_AtomToPrintableString(cx, atom);
         if (printable)
             js_ReportIsNotDefined(cx, printable);
         ok = JS_FALSE;
--- a/js/src/jsinterp.h
+++ b/js/src/jsinterp.h
@@ -355,20 +355,20 @@ js_GetPrimitiveThis(JSContext *cx, jsval
  */
 extern JSBool
 js_ComputeThis(JSContext *cx, jsval *argv);
 
 /*
  * NB: js_Invoke requires that cx is currently running JS (i.e., that cx->fp
  * is non-null), and that vp points to the callee, |this| parameter, and
  * actual arguments of the call. [vp .. vp + 2 + argc) must belong to the last
- * JS stack segment that js_AllocStack or js_AllocRawStack allocated. The
- * function may use the space available after vp + 2 + argc in the stack
- * segment for temporaries so the caller should not use that space for values
- * that must be preserved across the call.
+ * JS stack segment that js_AllocStack allocated. The function may use the
+ * space available after vp + 2 + argc in the stack segment for temporaries,
+ * so the caller should not use that space for values that must be preserved
+ * across the call.
  */
 extern JS_FRIEND_API(JSBool)
 js_Invoke(JSContext *cx, uintN argc, jsval *vp, uintN flags);
 
 /*
  * Consolidated js_Invoke flags simply rename certain JSFRAME_* flags, so that
  * we can share bits stored in JSStackFrame.flags and passed to:
  *
--- a/js/src/jsobj.c
+++ b/js/src/jsobj.c
@@ -1917,43 +1917,54 @@ js_CloneBlockObject(JSContext *cx, JSObj
     return clone;
 }
 
 /*
  * XXXblock this reverses a path in the property tree -- try to share
  *          the prototype's scope harder!
  */
 JSBool
-js_PutBlockObject(JSContext *cx, JSObject *obj)
+js_PutBlockObject(JSContext *cx, JSObject *obj, JSBool normalUnwind)
 {
     JSStackFrame *fp;
     uintN depth, slot;
     JSScopeProperty *sprop;
 
-    fp = (JSStackFrame *) JS_GetPrivate(cx, obj);
-    JS_ASSERT(fp);
-    depth = OBJ_BLOCK_DEPTH(cx, obj);
-    for (sprop = OBJ_SCOPE(obj)->lastProp; sprop; sprop = sprop->parent) {
-        if (sprop->getter != js_BlockClass.getProperty)
-            continue;
-        if (!(sprop->flags & SPROP_HAS_SHORTID))
-            continue;
-        slot = depth + (uintN)sprop->shortid;
-        JS_ASSERT(slot < fp->script->depth);
-        if (!js_DefineNativeProperty(cx, obj, sprop->id,
-                                     fp->spbase[slot], NULL, NULL,
-                                     JSPROP_ENUMERATE | JSPROP_PERMANENT,
-                                     SPROP_HAS_SHORTID, sprop->shortid,
-                                     NULL)) {
-            JS_SetPrivate(cx, obj, NULL);
-            return JS_FALSE;
+    if (normalUnwind) {
+        fp = (JSStackFrame *) JS_GetPrivate(cx, obj);
+        JS_ASSERT(fp);
+        depth = OBJ_BLOCK_DEPTH(cx, obj);
+        for (sprop = OBJ_SCOPE(obj)->lastProp; sprop; sprop = sprop->parent) {
+            if (sprop->getter != js_BlockClass.getProperty)
+                continue;
+            if (!(sprop->flags & SPROP_HAS_SHORTID))
+                continue;
+            slot = depth + (uintN)sprop->shortid;
+            JS_ASSERT(slot < fp->script->depth);
+            if (!js_DefineNativeProperty(cx, obj, sprop->id,
+                                         fp->spbase[slot], NULL, NULL,
+                                         JSPROP_ENUMERATE | JSPROP_PERMANENT,
+                                         SPROP_HAS_SHORTID, sprop->shortid,
+                                         NULL)) {
+                /*
+                 * Stop adding properties if we failed due to out-of-memory or
+                 * other quit-asap errors.
+                 */
+                if (!cx->throwing) {
+                    normalUnwind = JS_FALSE;
+                    goto out;
+                }
+            }
         }
     }
 
-    return JS_SetPrivate(cx, obj, NULL);
+  out:
+    /* We must clear the private slot even with errors. */
+    JS_SetPrivate(cx, obj, NULL);
+    return normalUnwind;
 }
 
 static JSBool
 block_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
 {
     JSStackFrame *fp;
     jsint slot;
 
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -345,17 +345,17 @@ js_NewWithObject(JSContext *cx, JSObject
 extern JSObject *
 js_NewBlockObject(JSContext *cx);
 
 extern JSObject *
 js_CloneBlockObject(JSContext *cx, JSObject *proto, JSObject *parent,
                     JSStackFrame *fp);
 
 extern JSBool
-js_PutBlockObject(JSContext *cx, JSObject *obj);
+js_PutBlockObject(JSContext *cx, JSObject *obj, JSBool normalUnwind);
 
 struct JSSharpObjectMap {
     jsrefcount  depth;
     jsatomid    sharpgen;
     JSHashTable *table;
 };
 
 #define SHARP_BIT       ((jsatomid) 1)
--- a/js/src/jsxdrapi.h
+++ b/js/src/jsxdrapi.h
@@ -197,17 +197,17 @@ JS_XDRFindClassById(JSXDRState *xdr, uin
  * Bytecode version number.  Decrement the second term whenever JS bytecode
  * changes incompatibly.
  *
  * This version number should be XDR'ed once near the front of any file or
  * larger storage unit containing XDR'ed bytecode and other data, and checked
  * before deserialization of bytecode.  If the saved version does not match
  * the current version, abort deserialization and invalidate the file.
  */
-#define JSXDR_BYTECODE_VERSION      (0xb973c0de - 19)
+#define JSXDR_BYTECODE_VERSION      (0xb973c0de - 20)
 
 /*
  * Library-private functions.
  */
 extern JSBool
 js_XDRAtom(JSXDRState *xdr, JSAtom **atomp);
 
 extern JSBool
--- a/js/src/win32.order
+++ b/js/src/win32.order
@@ -61,17 +61,16 @@ JS_ClearWatchPointsForObject ; 64155
 js_FinalizeObject ; 63925
 js_IndexAtom ; 63789
 JS_SetPrivate ; 63702
 JS_GetGlobalObject ; 63546
 js_Emit1 ; 63012
 JS_ContextIterator ; 57847
 JS_GetInstancePrivate ; 57817
 JS_HashTableRawRemove ; 57057
-js_AllocRawStack ; 54181
 js_Invoke ; 53568
 js_FindProperty ; 53150
 JS_GetFrameScript ; 51395
 js_LinkFunctionObject ; 50651
 js_SetSrcNoteOffset ; 47735
 js_InWithStatement ; 47346
 js_NewFunction ; 47074
 js_NewSrcNote2 ; 46165
@@ -118,17 +117,16 @@ js_NewScope ; 19991
 js_strlen ; 19070
 JS_GetScriptPrincipals ; 18063
 js_SrcNoteLength ; 17369
 js_DestroyObjectMap ; 17198
 js_DestroyScope ; 17198
 JS_GetStringLength ; 16306
 js_PopStatementCG ; 15418
 JS_GetFrameAnnotation ; 14949
-js_FreeRawStack ; 14032
 js_Interpret ; 14032
 js_TransferScopeLock ; 13899
 JS_ResolveStandardClass ; 13645
 JS_ResumeRequest ; 12837
 JS_SuspendRequest ; 12837
 JS_GetProperty ; 12488
 JS_NewObject ; 11660
 js_AllocTryNotes ; 11418