Bug 753145 - Attach static scope nesting information to scripts (r=jimb)
authorLuke Wagner <luke@mozilla.com>
Tue, 03 Jul 2012 10:24:36 -0700
changeset 99093 7221c50cb5b43f34c0aab6af24aef4c9b65d080a
parent 99092 ebc800948e7a3137c486974b1a350ab5b1eca916
child 99094 3923d008386d5b74926d020786afbf4253f02177
push id23102
push userryanvm@gmail.com
push dateFri, 13 Jul 2012 00:46:37 +0000
treeherdermozilla-central@6489be1890c0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb
bugs753145
milestone16.0a1
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 753145 - Attach static scope nesting information to scripts (r=jimb)
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/Parser.cpp
js/src/frontend/TreeContext-inl.h
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/tests/js1_5/extensions/regress-300079.js
js/src/vm/GlobalObject.cpp
js/src/vm/ScopeObject-inl.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/Stack.cpp
js/src/vm/Xdr.cpp
js/src/vm/Xdr.h
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -60,16 +60,17 @@ frontend::CompileScript(JSContext *cx, H
     SharedContext sc(cx, scopeChain, /* fun = */ NULL, /* funbox = */ NULL, StrictModeFromContext(cx));
 
     TreeContext tc(&parser, &sc, staticLevel, /* bodyid = */ 0);
     if (!tc.init())
         return NULL;
 
     bool savedCallerFun = compileAndGo && callerFrame && callerFrame->isFunctionFrame();
     Rooted<JSScript*> script(cx, JSScript::Create(cx,
+                                                  /* enclosingScope = */ NullPtr(),
                                                   savedCallerFun,
                                                   principals,
                                                   originPrincipals,
                                                   compileAndGo,
                                                   noScriptRval,
                                                   version,
                                                   staticLevel));
     if (!script)
@@ -226,16 +227,17 @@ frontend::CompileFunctionBody(JSContext 
     fun->setArgCount(funsc.bindings.numArgs());
 
     unsigned staticLevel = 0;
     TreeContext funtc(&parser, &funsc, staticLevel, /* bodyid = */ 0);
     if (!funtc.init())
         return false;
 
     Rooted<JSScript*> script(cx, JSScript::Create(cx,
+                                                  /* enclosingScope = */ NullPtr(),
                                                   /* savedCallerFun = */ false,
                                                   principals,
                                                   originPrincipals,
                                                   /* compileAndGo = */ false,
                                                   /* noScriptRval = */ false,
                                                   version,
                                                   staticLevel));
     if (!script)
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -668,22 +668,41 @@ BackPatch(JSContext *cx, BytecodeEmitter
 
 static void
 PushStatementBCE(BytecodeEmitter *bce, StmtInfoBCE *stmt, StmtType type, ptrdiff_t top)
 {
     SET_STATEMENT_TOP(stmt, top);
     PushStatement(bce, stmt, type);
 }
 
+/*
+ * Return the enclosing lexical scope, which is the innermost enclosing static
+ * block object or compiler created function.
+ */
+static JSObject *
+EnclosingStaticScope(BytecodeEmitter *bce)
+{
+    if (bce->blockChain)
+        return bce->blockChain;
+
+    if (!bce->sc->inFunction()) {
+        JS_ASSERT(!bce->parent);
+        return NULL;
+    }
+
+    return bce->sc->fun();
+}
+
 // Push a block scope statement and link blockObj into bce->blockChain.
 static void
 PushBlockScopeBCE(BytecodeEmitter *bce, StmtInfoBCE *stmt, StaticBlockObject &blockObj,
                   ptrdiff_t top)
 {
     PushStatementBCE(bce, stmt, STMT_BLOCK, top);
+    blockObj.initEnclosingStaticScope(EnclosingStaticScope(bce));
     FinishPushBlockScope(bce, stmt, blockObj);
 }
 
 // Patches |breaks| and |continues| unless the top statement info record
 // represents a try-catch-finally suite. May fail if a jump offset overflows.
 static bool
 PopStatementBCE(JSContext *cx, BytecodeEmitter *bce)
 {
@@ -4214,17 +4233,16 @@ EmitIf(JSContext *cx, BytecodeEmitter *b
  *
  *  bytecode          stackDepth  srcnotes
  *  evaluate a        +1
  *  evaluate b        +1
  *  dup               +1          SRC_DESTRUCTLET + offset to enterlet0
  *  destructure y
  *  pick 1
  *  dup               +1          SRC_DESTRUCTLET + offset to enterlet0
- *  pick
  *  destructure z
  *  pick 1
  *  pop               -1
  *  enterlet0                     SRC_DECL + offset to leaveblockexpr
  *  evaluate e        +1
  *  leaveblockexpr    -3          SRC_PCBASE + offset to evaluate a
  *
  * Note that, since enterlet0 simply changes fp->blockChain and does not
@@ -4822,17 +4840,19 @@ EmitFunc(JSContext *cx, BytecodeEmitter 
         sc.cxFlags = funbox->cxFlags;
         if (bce->sc->funMightAliasLocals())
             sc.setFunMightAliasLocals();  // inherit funMightAliasLocals from parent
         sc.bindings.transfer(&funbox->bindings);
         JS_ASSERT_IF(bce->sc->inStrictMode(), sc.inStrictMode());
 
         // Inherit most things (principals, version, etc) from the parent.
         Rooted<JSScript*> parent(cx, bce->script);
+        Rooted<JSObject*> enclosingScope(cx, EnclosingStaticScope(bce));
         Rooted<JSScript*> script(cx, JSScript::Create(cx,
+                                                      enclosingScope,
                                                       /* savedCallerFun = */ false,
                                                       parent->principals,
                                                       parent->originPrincipals,
                                                       parent->compileAndGo,
                                                       /* noScriptRval = */ false,
                                                       parent->getVersion(),
                                                       parent->staticLevel + 1));
         if (!script)
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -97,24 +97,16 @@ StrictModeGetter::setQueuedStrictModeErr
 static void
 PushStatementTC(TreeContext *tc, StmtInfoTC *stmt, StmtType type)
 {
     stmt->blockid = tc->blockid();
     PushStatement(tc, stmt, type);
     stmt->isFunctionBodyBlock = false;
 }
 
-// Push a block scope statement and link blockObj into tc->blockChain.
-static void
-PushBlockScopeTC(TreeContext *tc, StmtInfoTC *stmt, StaticBlockObject &blockObj)
-{
-    PushStatementTC(tc, stmt, STMT_BLOCK);
-    FinishPushBlockScope(tc, stmt, blockObj);
-}
-
 Parser::Parser(JSContext *cx, JSPrincipals *prin, JSPrincipals *originPrin,
                const jschar *chars, size_t length, const char *fn, unsigned ln, JSVersion v,
                bool foldConstants, bool compileAndGo)
   : AutoGCRooter(cx, PARSER),
     context(cx),
     strictModeGetter(this),
     tokenStream(cx, prin, originPrin, chars, length, fn, ln, v, &strictModeGetter),
     tempPoolMark(NULL),
@@ -2139,22 +2131,26 @@ struct RemoveDecl {
         tc->decls.remove(atom);
         return true;
     }
 };
 
 static void
 PopStatementTC(TreeContext *tc)
 {
-    if (tc->topStmt->isBlockScope) {
-        StaticBlockObject &blockObj = *tc->topStmt->blockObj;
-        JS_ASSERT(!blockObj.inDictionaryMode());
-        ForEachLetDef(tc, blockObj, RemoveDecl());
+    StaticBlockObject *blockObj = tc->topStmt->blockObj;
+    JS_ASSERT(!!blockObj == (tc->topStmt->isBlockScope));
+
+    FinishPopStatement(tc);
+
+    if (blockObj) {
+        JS_ASSERT(!blockObj->inDictionaryMode());
+        ForEachLetDef(tc, *blockObj, RemoveDecl());
+        blockObj->resetPrevBlockChainFromParser();
     }
-    FinishPopStatement(tc);
 }
 
 static inline bool
 OuterLet(TreeContext *tc, StmtInfoTC *stmt, HandleAtom atom)
 {
     while (stmt->downScope) {
         stmt = LexicalLookup(tc, atom, NULL, stmt->downScope);
         if (!stmt)
@@ -2753,32 +2749,37 @@ Parser::returnOrYield(bool useAssignExpr
     {
         return NULL;
     }
 
     return pn;
 }
 
 static ParseNode *
-PushLexicalScope(JSContext *cx, Parser *parser, StaticBlockObject &obj, StmtInfoTC *stmt)
+PushLexicalScope(JSContext *cx, Parser *parser, StaticBlockObject &blockObj, StmtInfoTC *stmt)
 {
     ParseNode *pn = LexicalScopeNode::create(PNK_LEXICALSCOPE, parser);
     if (!pn)
         return NULL;
 
-    ObjectBox *blockbox = parser->newObjectBox(&obj);
+    ObjectBox *blockbox = parser->newObjectBox(&blockObj);
     if (!blockbox)
         return NULL;
 
-    PushBlockScopeTC(parser->tc, stmt, obj);
+    TreeContext *tc = parser->tc;
+
+    PushStatementTC(tc, stmt, STMT_BLOCK);
+    blockObj.initPrevBlockChainFromParser(tc->blockChain);
+    FinishPushBlockScope(tc, stmt, blockObj);
+
     pn->setOp(JSOP_LEAVEBLOCK);
     pn->pn_objbox = blockbox;
     pn->pn_cookie.makeFree();
     pn->pn_dflags = 0;
-    if (!GenerateBlockId(parser->tc, stmt->blockid))
+    if (!GenerateBlockId(tc, stmt->blockid))
         return NULL;
     pn->pn_blockid = stmt->blockid;
     return pn;
 }
 
 static ParseNode *
 PushLexicalScope(JSContext *cx, Parser *parser, StmtInfoTC *stmt)
 {
@@ -3770,17 +3771,17 @@ Parser::letStatement()
              * list stack, if it isn't already there.  If it is there, but it
              * lacks the SIF_SCOPE flag, it must be a try, catch, or finally
              * block.
              */
             stmt->isBlockScope = true;
             stmt->downScope = tc->topScopeStmt;
             tc->topScopeStmt = stmt;
 
-            blockObj->setEnclosingBlock(tc->blockChain);
+            blockObj->initPrevBlockChainFromParser(tc->blockChain);
             tc->blockChain = blockObj;
             stmt->blockObj = blockObj;
 
 #ifdef DEBUG
             ParseNode *tmp = tc->blockNode;
             JS_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE));
 #endif
 
--- a/js/src/frontend/TreeContext-inl.h
+++ b/js/src/frontend/TreeContext-inl.h
@@ -140,17 +140,16 @@ frontend::PushStatement(ContextT *ct, ty
 }
 
 template <class ContextT>
 void
 frontend::FinishPushBlockScope(ContextT *ct, typename ContextT::StmtInfo *stmt,
                                StaticBlockObject &blockObj)
 {
     stmt->isBlockScope = true;
-    blockObj.setEnclosingBlock(ct->blockChain);
     stmt->downScope = ct->topScopeStmt;
     ct->topScopeStmt = stmt;
     ct->blockChain = &blockObj;
     stmt->blockObj = &blockObj;
 }
 
 template <class ContextT>
 void
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4661,18 +4661,25 @@ JS_CloneFunctionObject(JSContext *cx, JS
         JS_ASSERT(parent);
     }
 
     if (!funobj->isFunction()) {
         ReportIsNotFunction(cx, ObjectValue(*funobj));
         return NULL;
     }
 
+    /*
+     * If a function was compiled as compile-and-go or was compiled to be
+     * lexically nested inside some other script, we cannot clone it without
+     * breaking the compiler's assumptions.
+     */
     RootedFunction fun(cx, funobj->toFunction());
-    if (fun->isInterpreted() && fun->script()->compileAndGo) {
+    if (fun->isInterpreted() &&
+        (fun->script()->compileAndGo || fun->script()->enclosingStaticScope()))
+    {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                              JSMSG_BAD_CLONE_FUNOBJ_SCOPE);
         return NULL;
     }
 
     if (fun->isBoundFunction()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                              JSMSG_CANT_CLONE_OBJECT);
@@ -5372,17 +5379,17 @@ JS_ExecuteScript(JSContext *cx, JSObject
      * Mozilla caches pre-compiled scripts (e.g., in the XUL prototype cache)
      * and runs them against multiple globals. With a compartment per global,
      * this requires cloning the pre-compiled script into each new global.
      * Since each script gets run once, there is no point in trying to cache
      * this clone. Ideally, this would be handled at some pinch point in
      * mozilla, but there doesn't seem to be one, so we handle it here.
      */
     if (scriptArg->compartment() != obj->compartment()) {
-        script = CloneScript(cx, scriptArg);
+        script = CloneScript(cx, NullPtr(), NullPtr(), scriptArg);
         if (!script.get())
             return false;
     } else {
         script = scriptArg;
     }
 
     return Execute(cx, script.get(), *obj, rval);
 }
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4640,16 +4640,20 @@ extern JS_PUBLIC_API(JSFunction *)
 JS_DefineUCFunction(JSContext *cx, JSObject *obj,
                     const jschar *name, size_t namelen, JSNative call,
                     unsigned nargs, unsigned attrs);
 
 extern JS_PUBLIC_API(JSFunction *)
 JS_DefineFunctionById(JSContext *cx, JSObject *obj, jsid id, JSNative call,
                       unsigned nargs, unsigned attrs);
 
+/*
+ * Clone a top-level function into a new scope. This function will dynamically
+ * fail if funobj was lexically nested inside some other function.
+ */
 extern JS_PUBLIC_API(JSObject *)
 JS_CloneFunctionObject(JSContext *cx, JSObject *funobj, JSObject *parent);
 
 /*
  * Methods usually act upon |this| objects only from a single global object and
  * compartment.  Sometimes, however, a method must act upon |this| values from
  * multiple global objects or compartments.  In such cases the |this| value a
  * method might see will be wrapped, such that various access to the object --
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -331,17 +331,18 @@ fun_resolve(JSContext *cx, HandleObject 
         }
     }
 
     return true;
 }
 
 template<XDRMode mode>
 bool
-js::XDRInterpretedFunction(XDRState<mode> *xdr, JSObject **objp, JSScript *parentScript)
+js::XDRInterpretedFunction(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript enclosingScript,
+                           JSObject **objp)
 {
     /* NB: Keep this in sync with CloneInterpretedFunction. */
     JSAtom *atom;
     uint32_t firstword;           /* flag telling whether fun->atom is non-null,
                                    plus for fun->u.i.skipmin, fun->u.i.wrapper,
                                    and 14 bits reserved for future use */
     uint32_t flagsword;           /* word for argument count and fun->flags */
 
@@ -377,17 +378,17 @@ js::XDRInterpretedFunction(XDRState<mode
 
     if (!xdr->codeUint32(&firstword))
         return false;
     if ((firstword & 1U) && !XDRAtom(xdr, &atom))
         return false;
     if (!xdr->codeUint32(&flagsword))
         return false;
 
-    if (!XDRScript(xdr, &script, parentScript))
+    if (!XDRScript(xdr, enclosingScope, enclosingScript, fun, &script))
         return false;
 
     if (mode == XDR_DECODE) {
         fun->nargs = flagsword >> 16;
         JS_ASSERT((flagsword & JSFUN_KINDMASK) >= JSFUN_INTERPRETED);
         fun->flags = uint16_t(flagsword);
         fun->atom.init(atom);
         fun->initScript(script);
@@ -398,37 +399,37 @@ js::XDRInterpretedFunction(XDRState<mode
         js_CallNewScriptHook(cx, fun->script(), fun);
         *objp = fun;
     }
 
     return true;
 }
 
 template bool
-js::XDRInterpretedFunction(XDRState<XDR_ENCODE> *xdr, JSObject **objp, JSScript *parentScript);
+js::XDRInterpretedFunction(XDRState<XDR_ENCODE> *, HandleObject, HandleScript, JSObject **);
 
 template bool
-js::XDRInterpretedFunction(XDRState<XDR_DECODE> *xdr, JSObject **objp, JSScript *parentScript);
+js::XDRInterpretedFunction(XDRState<XDR_DECODE> *, HandleObject, HandleScript, JSObject **);
 
 JSObject *
-js::CloneInterpretedFunction(JSContext *cx, HandleFunction srcFun)
+js::CloneInterpretedFunction(JSContext *cx, HandleObject enclosingScope, HandleFunction srcFun)
 {
     /* NB: Keep this in sync with XDRInterpretedFunction. */
 
     RootedObject parent(cx, NULL);
     RootedFunction clone(cx, js_NewFunction(cx, NULL, NULL, 0, JSFUN_INTERPRETED, parent, NULL));
     if (!clone)
         return NULL;
     if (!JSObject::clearParent(cx, clone))
         return NULL;
     if (!JSObject::clearType(cx, clone))
         return NULL;
 
     Rooted<JSScript*> srcScript(cx, srcFun->script());
-    JSScript *clonedScript = CloneScript(cx, srcScript);
+    JSScript *clonedScript = CloneScript(cx, enclosingScope, clone, srcScript);
     if (!clonedScript)
         return NULL;
 
     clone->nargs = srcFun->nargs;
     clone->flags = srcFun->flags;
     clone->atom.init(srcFun->atom);
     clone->initScript(clonedScript);
     clonedScript->setFunction(clone);
@@ -1276,26 +1277,30 @@ js_CloneFunctionObject(JSContext *cx, Ha
          * will have been caught by CloneFunctionObject coming from function
          * definitions or read barriers, so will not get here.
          */
         if (fun->getProto() == proto && !fun->hasSingletonType())
             clone->setType(fun->type());
     } else {
         /*
          * Across compartments we have to clone the script for interpreted
-         * functions.
+         * functions. Cross-compartment cloning only happens via JSAPI
+         * (JS_CloneFunctionObject) which dynamically ensures that 'script' has
+         * no enclosing lexical scope (only the global scope).
          */
         if (clone->isInterpreted()) {
             RootedScript script(cx, clone->script());
             JS_ASSERT(script);
             JS_ASSERT(script->compartment() == fun->compartment());
             JS_ASSERT(script->compartment() != cx->compartment);
+            JS_ASSERT(!script->enclosingStaticScope());
 
             clone->mutableScript().init(NULL);
-            JSScript *cscript = CloneScript(cx, script);
+
+            JSScript *cscript = CloneScript(cx, NullPtr(), clone, script);
             if (!cscript)
                 return NULL;
 
             clone->setScript(cscript);
             cscript->setFunction(clone);
             if (!clone->setTypeForScriptedFunction(cx))
                 return NULL;
 
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -67,16 +67,17 @@ struct JSFunction : public JSObject
     bool hasRest()           const { return flags & JSFUN_HAS_REST; }
     bool isInterpreted()     const { return kind() >= JSFUN_INTERPRETED; }
     bool isNative()          const { return !isInterpreted(); }
     bool isNativeConstructor() const { return flags & JSFUN_CONSTRUCTOR; }
     bool isHeavyweight()     const { return JSFUN_HEAVYWEIGHT_TEST(flags); }
     bool isNullClosure()     const { return kind() == JSFUN_NULL_CLOSURE; }
     bool isFunctionPrototype() const { return flags & JSFUN_PROTOTYPE; }
     bool isInterpretedConstructor() const { return isInterpreted() && !isFunctionPrototype(); }
+    bool isNamedLambda()     const { return (flags & JSFUN_LAMBDA) && atom; }
 
     uint16_t kind()          const { return flags & JSFUN_KINDMASK; }
     void setKind(uint16_t k) {
         JS_ASSERT(!(k & ~JSFUN_KINDMASK));
         flags = (flags & ~JSFUN_KINDMASK) | k;
     }
 
     /* Returns the strictness of this function, which must be interpreted. */
@@ -248,27 +249,25 @@ JSFunction::toExtended()
 
 inline const js::FunctionExtended *
 JSFunction::toExtended() const
 {
     JS_ASSERT(isExtended());
     return static_cast<const js::FunctionExtended *>(this);
 }
 
-inline bool
-js_IsNamedLambda(JSFunction *fun) { return (fun->flags & JSFUN_LAMBDA) && fun->atom; }
-
 namespace js {
 
 template<XDRMode mode>
 bool
-XDRInterpretedFunction(XDRState<mode> *xdr, JSObject **objp, JSScript *parentScript);
+XDRInterpretedFunction(XDRState<mode> *xdr, HandleObject enclosingScope,
+                       HandleScript enclosingScript, JSObject **objp);
 
 extern JSObject *
-CloneInterpretedFunction(JSContext *cx, HandleFunction fun);
+CloneInterpretedFunction(JSContext *cx, HandleObject enclosingScope, HandleFunction fun);
 
 } /* namespace js */
 
 extern JSBool
 js_fun_apply(JSContext *cx, unsigned argc, js::Value *vp);
 
 extern JSBool
 js_fun_call(JSContext *cx, unsigned argc, js::Value *vp);
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -46,18 +46,16 @@
 #include "vm/RegExpObject-inl.h"
 
 #include "frontend/TreeContext-inl.h"
 
 using namespace js;
 using namespace js::gc;
 using namespace js::frontend;
 
-namespace js {
-
 BindingKind
 Bindings::lookup(JSContext *cx, JSAtom *name, unsigned *indexp) const
 {
     if (!lastBinding)
         return NONE;
 
     Shape **spp;
     Shape *shape = Shape::search(cx, lastBinding, AtomToId(name), &spp);
@@ -242,18 +240,16 @@ Bindings::lastVariable() const
 
 void
 Bindings::trace(JSTracer *trc)
 {
     if (lastBinding)
         MarkShape(trc, &lastBinding, "shape");
 }
 
-} /* namespace js */
-
 template<XDRMode mode>
 static bool
 XDRScriptConst(XDRState<mode> *xdr, HeapValue *vp)
 {
     /*
      * A script constant can be an arbitrary primitive value as they are used
      * to implement JSOP_LOOKUPSWITCH. But they cannot be objects, see
      * bug 407186.
@@ -337,19 +333,35 @@ XDRScriptConst(XDRState<mode> *xdr, Heap
       case SCRIPT_VOID:
         if (mode == XDR_DECODE)
             vp->init(UndefinedValue());
         break;
     }
     return true;
 }
 
+static inline uint32_t
+FindBlockIndex(JSScript *script, StaticBlockObject &block)
+{
+    ObjectArray *objects = script->objects();
+    HeapPtrObject *vector = objects->vector;
+    unsigned length = objects->length;
+    for (unsigned i = 0; i < length; ++i) {
+        if (vector[i] == &block)
+            return i;
+    }
+
+    JS_NOT_REACHED("Block not found");
+    return UINT32_MAX;
+}
+
 template<XDRMode mode>
 bool
-js::XDRScript(XDRState<mode> *xdr, JSScript **scriptp, JSScript *parentScript)
+js::XDRScript(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript enclosingScript,
+              HandleFunction fun, JSScript **scriptp)
 {
     /* NB: Keep this in sync with CloneScript. */
 
     enum ScriptBits {
         NoScriptRval,
         SavedCallerFun,
         StrictModeCode,
         ContainsDynamicNameAccess,
@@ -367,26 +379,22 @@ js::XDRScript(XDRState<mode> *xdr, JSScr
     uint32_t nTypeSets = 0;
     uint32_t scriptBits = 0;
 
     JSContext *cx = xdr->cx();
     Rooted<JSScript*> script(cx);
     nsrcnotes = ntrynotes = natoms = nobjects = nregexps = nconsts = nClosedArgs = nClosedVars = 0;
     jssrcnote *notes = NULL;
 
-    /* XDR arguments, var vars, and upvars. */
-    uint16_t nargs, nvars;
-#if defined(DEBUG) || defined(__GNUC__) /* quell GCC overwarning */
-    script = NULL;
-    nargs = nvars = Bindings::BINDING_COUNT_LIMIT;
-#endif
-    uint32_t argsVars;
+    /* XDR arguments and vars. */
+    uint16_t nargs = 0, nvars = 0;
+    uint32_t argsVars = 0;
     if (mode == XDR_ENCODE) {
         script = *scriptp;
-        JS_ASSERT_IF(parentScript, parentScript->compartment() == script->compartment());
+        JS_ASSERT_IF(enclosingScript, enclosingScript->compartment() == script->compartment());
 
         nargs = script->bindings.numArgs();
         nvars = script->bindings.numVars();
         argsVars = (nargs << 16) | nvars;
     }
     if (!xdr->codeUint32(&argsVars))
         return false;
     if (mode == XDR_DECODE) {
@@ -510,17 +518,17 @@ js::XDRScript(XDRState<mode> *xdr, JSScr
             scriptBits |= (1 << ContainsDynamicNameAccess);
         if (script->funHasExtensibleScope)
             scriptBits |= (1 << FunHasExtensibleScope);
         if (script->argumentsHasVarBinding())
             scriptBits |= (1 << ArgumentsHasVarBinding);
         if (script->analyzedArgsUsage() && script->needsArgsObj())
             scriptBits |= (1 << NeedsArgsObj);
         if (script->filename) {
-            scriptBits |= (parentScript && parentScript->filename == script->filename)
+            scriptBits |= (enclosingScript && enclosingScript->filename == script->filename)
                           ? (1 << ParentFilename)
                           : (1 << OwnFilename);
         }
         if (script->isGenerator)
             scriptBits |= (1 << IsGenerator);
 
         JS_ASSERT(!script->compileAndGo);
         JS_ASSERT(!script->hasSingletons);
@@ -559,16 +567,17 @@ js::XDRScript(XDRState<mode> *xdr, JSScr
     if (mode == XDR_DECODE) {
         /* Note: version is packed into the 32b space with another 16b value. */
         JSVersion version_ = JSVersion(version & JS_BITMASK(16));
         JS_ASSERT((version_ & VersionFlags::FULL_MASK) == unsigned(version_));
 
         // principals and originPrincipals are set with xdr->initScriptPrincipals(script) below.
         // staticLevel is set below.
         script = JSScript::Create(cx,
+                                  enclosingScope,
                                   !!(scriptBits & (1 << SavedCallerFun)),
                                   /* principals = */ NULL,
                                   /* originPrincipals = */ NULL,
                                   /* compileAndGo = */ false,
                                   !!(scriptBits & (1 << NoScriptRval)),
                                   version_,
                                   /* staticLevel = */ 0);
         if (!script || !JSScript::partiallyInit(cx, script,
@@ -616,19 +625,19 @@ js::XDRScript(XDRState<mode> *xdr, JSScr
         if (!xdr->codeCString(&filename))
             return false;
         if (mode == XDR_DECODE) {
             script->filename = SaveScriptFilename(cx, filename);
             if (!script->filename)
                 return false;
         }
     } else if (scriptBits & (1 << ParentFilename)) {
-        JS_ASSERT(parentScript);
+        JS_ASSERT(enclosingScript);
         if (mode == XDR_DECODE)
-            script->filename = parentScript->filename;
+            script->filename = enclosingScript->filename;
     }
 
     if (mode == XDR_DECODE) {
         script->lineno = lineno;
         script->nslots = uint16_t(nslots);
         script->staticLevel = uint16_t(nslots >> 16);
         xdr->initScriptPrincipals(script);
     }
@@ -642,40 +651,83 @@ js::XDRScript(XDRState<mode> *xdr, JSScr
         } else {
             JSAtom *tmp = script->atoms[i];
             if (!XDRAtom(xdr, &tmp))
                 return false;
         }
     }
 
     /*
-     * Here looping from 0-to-length to xdr objects is essential. It ensures
-     * that block objects from the script->objects array will be written and
-     * restored in the outer-to-inner order. js_XDRBlockObject relies on this
-     * to restore the parent chain.
+     * Here looping from 0-to-length to xdr objects is essential to ensure that
+     * all references to enclosing blocks (via FindBlockIndex below) happen
+     * after the enclosing block has been XDR'd.
      */
     for (i = 0; i != nobjects; ++i) {
         HeapPtr<JSObject> *objp = &script->objects()->vector[i];
         uint32_t isBlock;
         if (mode == XDR_ENCODE) {
             JSObject *obj = *objp;
             JS_ASSERT(obj->isFunction() || obj->isStaticBlock());
             isBlock = obj->isBlock() ? 1 : 0;
         }
         if (!xdr->codeUint32(&isBlock))
             return false;
         if (isBlock == 0) {
+            /* Code the nested function's enclosing scope. */
+            uint32_t funEnclosingScopeIndex = 0;
+            if (mode == XDR_ENCODE) {
+                StaticScopeIter ssi((*objp)->toFunction()->script()->enclosingStaticScope());
+                if (ssi.done() || ssi.type() == StaticScopeIter::FUNCTION) {
+                    JS_ASSERT(ssi.done() == !fun);
+                    funEnclosingScopeIndex = UINT32_MAX;
+                } else {
+                    funEnclosingScopeIndex = FindBlockIndex(script, ssi.block());
+                    JS_ASSERT(funEnclosingScopeIndex < i);
+                }
+            }
+            if (!xdr->codeUint32(&funEnclosingScopeIndex))
+                return false;
+            Rooted<JSObject*> funEnclosingScope(cx);
+            if (mode == XDR_DECODE) {
+                if (funEnclosingScopeIndex == UINT32_MAX) {
+                    funEnclosingScope = fun;
+                } else {
+                    JS_ASSERT(funEnclosingScopeIndex < i);
+                    funEnclosingScope = script->objects()->vector[funEnclosingScopeIndex];
+                }
+            }
+
             JSObject *tmp = *objp;
-            if (!XDRInterpretedFunction(xdr, &tmp, parentScript))
+            if (!XDRInterpretedFunction(xdr, funEnclosingScope, script, &tmp))
                 return false;
             *objp = tmp;
         } else {
+            /* Code the nested block's enclosing scope. */
             JS_ASSERT(isBlock == 1);
+            uint32_t blockEnclosingScopeIndex = 0;
+            if (mode == XDR_ENCODE) {
+                if (StaticBlockObject *block = (*objp)->asStaticBlock().enclosingBlock())
+                    blockEnclosingScopeIndex = FindBlockIndex(script, *block);
+                else
+                    blockEnclosingScopeIndex = UINT32_MAX;
+            }
+            if (!xdr->codeUint32(&blockEnclosingScopeIndex))
+                return false;
+            Rooted<JSObject*> blockEnclosingScope(cx);
+            if (mode == XDR_DECODE) {
+                if (blockEnclosingScopeIndex != UINT32_MAX) {
+                    JS_ASSERT(blockEnclosingScopeIndex < i);
+                    blockEnclosingScope = script->objects()->vector[blockEnclosingScopeIndex];
+                } else {
+                    blockEnclosingScope = fun;
+                }
+            }
+
             StaticBlockObject *tmp = static_cast<StaticBlockObject *>(objp->get());
-            if (!XDRStaticBlockObject(xdr, script, &tmp))
+            if (!XDRStaticBlockObject(xdr, blockEnclosingScope, script, &tmp))
                 return false;
             *objp = tmp;
         }
     }
     for (i = 0; i != nregexps; ++i) {
         if (!XDRScriptRegExpObject(xdr, &script->regexps()->vector[i]))
             return false;
     }
@@ -732,20 +784,20 @@ js::XDRScript(XDRState<mode> *xdr, JSScr
             (void) script->initScriptCounts(cx);
         *scriptp = script;
     }
 
     return true;
 }
 
 template bool
-js::XDRScript(XDRState<XDR_ENCODE> *xdr, JSScript **scriptp, JSScript *parentScript);
+js::XDRScript(XDRState<XDR_ENCODE> *, HandleObject, HandleScript, HandleFunction, JSScript **);
 
 template bool
-js::XDRScript(XDRState<XDR_DECODE> *xdr, JSScript **scriptp, JSScript *parentScript);
+js::XDRScript(XDRState<XDR_DECODE> *, HandleObject, HandleScript, HandleFunction, JSScript **);
 
 bool
 JSScript::initScriptCounts(JSContext *cx)
 {
     JS_ASSERT(!hasScriptCounts);
 
     size_t n = 0;
 
@@ -1068,42 +1120,43 @@ ScriptDataSize(uint32_t length, uint32_t
         size += sizeof(ClosedSlotArray) + nClosedVars * sizeof(uint32_t);
 
     size += length * sizeof(jsbytecode);
     size += nsrcnotes * sizeof(jssrcnote);
     return size;
 }
 
 JSScript *
-JSScript::Create(JSContext *cx, bool savedCallerFun, JSPrincipals *principals,
-                 JSPrincipals *originPrincipals, bool compileAndGo, bool noScriptRval,
-                 JSVersion version, unsigned staticLevel)
+JSScript::Create(JSContext *cx, HandleObject enclosingScope, bool savedCallerFun,
+                 JSPrincipals *principals, JSPrincipals *originPrincipals,
+                 bool compileAndGo, bool noScriptRval, JSVersion version, unsigned staticLevel)
 {
     JSScript *script = js_NewGCScript(cx);
     if (!script)
         return NULL;
 
     PodZero(script);
 
+    script->enclosingScope_ = enclosingScope;
     script->savedCallerFun = savedCallerFun;
 
     /* Establish invariant: principals implies originPrincipals. */
     if (principals) {
         script->principals = principals;
         script->originPrincipals = originPrincipals ? originPrincipals : principals;
         JS_HoldPrincipals(script->principals);
         JS_HoldPrincipals(script->originPrincipals);
     } else if (originPrincipals) {
         script->originPrincipals = originPrincipals;
         JS_HoldPrincipals(script->originPrincipals);
     }
 
     script->compileAndGo = compileAndGo;
     script->noScriptRval = noScriptRval;
- 
+
     script->version = version;
     JS_ASSERT(script->getVersion() == version);     // assert that no overflow occurred
 
     // This is an unsigned-to-uint16_t conversion, test for too-high values.
     // In practice, recursion in Parser and/or BytecodeEmitter will blow the
     // stack if we nest functions more than a few hundred deep, so this will
     // never trigger.  Oh well.
     if (staticLevel > UINT16_MAX) {
@@ -1309,17 +1362,17 @@ JSScript::fullyInitFromEmitter(JSContext
     if (cx->compartment->debugMode())
         script->debugMode = true;
 #endif
 
     if (bce->sc->inFunction()) {
         if (bce->sc->funArgumentsHasLocalBinding()) {
             // This must precede the script->bindings.transfer() call below
             script->setArgumentsHasVarBinding();
-            if (bce->sc->funDefinitelyNeedsArgsObj())        
+            if (bce->sc->funDefinitelyNeedsArgsObj())
                 script->setNeedsArgsObj(true);
         } else {
             JS_ASSERT(!bce->sc->funDefinitelyNeedsArgsObj());
         }
     }
 
     if (nClosedArgs)
         PodCopy<uint32_t>(script->closedArgs()->vector, &bce->closedArgs[0], nClosedArgs);
@@ -1641,17 +1694,17 @@ template <class T>
 static inline T *
 Rebase(JSScript *dst, JSScript *src, T *srcp)
 {
     size_t off = reinterpret_cast<uint8_t *>(srcp) - src->data;
     return reinterpret_cast<T *>(dst->data + off);
 }
 
 JSScript *
-js::CloneScript(JSContext *cx, HandleScript src)
+js::CloneScript(JSContext *cx, HandleObject enclosingScope, HandleFunction fun, HandleScript src)
 {
     /* NB: Keep this in sync with XDRScript. */
 
     uint32_t nconsts   = src->hasConsts()   ? src->consts()->length   : 0;
     uint32_t nobjects  = src->hasObjects()  ? src->objects()->length  : 0;
     uint32_t nregexps  = src->hasRegexps()  ? src->regexps()->length  : 0;
     uint32_t ntrynotes = src->hasTrynotes() ? src->trynotes()->length : 0;
     uint32_t nClosedArgs = src->numClosedArgs();
@@ -1690,23 +1743,39 @@ js::CloneScript(JSContext *cx, HandleScr
         return NULL;
 
     /* Objects */
 
     AutoObjectVector objects(cx);
     if (nobjects != 0) {
         HeapPtrObject *vector = src->objects()->vector;
         for (unsigned i = 0; i < nobjects; i++) {
+            JSObject &obj = *vector[i];
             JSObject *clone;
-            if (vector[i]->isStaticBlock()) {
-                Rooted<StaticBlockObject*> block(cx, &vector[i]->asStaticBlock());
-                clone = CloneStaticBlockObject(cx, block, objects, src);
+            if (obj.isStaticBlock()) {
+                Rooted<StaticBlockObject*> innerBlock(cx, &obj.asStaticBlock());
+
+                Rooted<JSObject*> enclosingScope(cx);
+                if (StaticBlockObject *enclosingBlock = innerBlock->enclosingBlock())
+                    enclosingScope = objects[FindBlockIndex(src, *enclosingBlock)];
+                else
+                    enclosingScope = fun;
+
+                clone = CloneStaticBlockObject(cx, enclosingScope, innerBlock);
             } else {
-                RootedFunction fun(cx, vector[i]->toFunction());
-                clone = CloneInterpretedFunction(cx, fun);
+                Rooted<JSFunction*> innerFun(cx, obj.toFunction());
+
+                StaticScopeIter ssi(innerFun->script()->enclosingStaticScope());
+                Rooted<JSObject*> enclosingScope(cx);
+                if (!ssi.done() && ssi.type() == StaticScopeIter::BLOCK)
+                    enclosingScope = objects[FindBlockIndex(src, ssi.block())];
+                else
+                    enclosingScope = fun;
+
+                clone = CloneInterpretedFunction(cx, enclosingScope, innerFun);
             }
             if (!clone || !objects.append(clone))
                 return NULL;
         }
     }
 
     /* RegExps */
 
@@ -1717,17 +1786,17 @@ js::CloneScript(JSContext *cx, HandleScr
             JSObject *clone = CloneScriptRegExpObject(cx, vector[i]->asRegExp());
             if (!clone || !regexps.append(clone))
                 return NULL;
         }
     }
 
     /* Now that all fallible allocation is complete, create the GC thing. */
 
-    JSScript *dst = JSScript::Create(cx, src->savedCallerFun,
+    JSScript *dst = JSScript::Create(cx, enclosingScope, src->savedCallerFun,
                                      cx->compartment->principals, src->originPrincipals,
                                      src->compileAndGo, src->noScriptRval,
                                      src->getVersion(), src->staticLevel);
     if (!dst) {
         Foreground::free_(data);
         return NULL;
     }
 
@@ -2044,16 +2113,19 @@ JSScript::markChildren(JSTracer *trc)
     if (hasConsts()) {
         ConstArray *constarray = consts();
         MarkValueRange(trc, constarray->length, constarray->vector, "consts");
     }
 
     if (function())
         MarkObject(trc, &function_, "function");
 
+    if (enclosingScope_)
+        MarkObject(trc, &enclosingScope_, "enclosing");
+
     if (IS_GC_MARKING_TRACER(trc) && filename)
         MarkScriptFilename(trc->runtime, filename);
 
     bindings.trace(trc);
 
 #ifdef JS_METHODJIT
     for (int constructing = 0; constructing <= 1; constructing++) {
         for (int barriers = 0; barriers <= 1; barriers++) {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -409,18 +409,18 @@ struct JSScript : public js::gc::Cell
 
     /* Persistent type information retained across GCs. */
     js::types::TypeScript *types;
 
   private:
 #ifdef JS_METHODJIT
     JITScriptSet *jitInfo;
 #endif
-
     js::HeapPtrFunction function_;
+    js::HeapPtrObject   enclosingScope_;
 
     // 32-bit fields.
 
   public:
     uint32_t        length;     /* length of code vector */
 
     uint32_t        lineno;     /* base line number of script */
 
@@ -429,20 +429,16 @@ struct JSScript : public js::gc::Cell
 
     uint32_t        natoms;     /* length of atoms array */
 
   private:
     uint32_t        useCount;   /* Number of times the script has been called
                                  * or has had backedges taken. Reset if the
                                  * script's JIT code is forcibly discarded. */
 
-#if JS_BITS_PER_WORD == 32
-    uint32_t        pad32;
-#endif
-
 #ifdef DEBUG
     // Unique identifier within the compartment for this script, used for
     // printing analysis information.
     uint32_t        id_;
   private:
     uint32_t        idpad;
 #endif
 
@@ -521,17 +517,17 @@ struct JSScript : public js::gc::Cell
     bool            needsArgsAnalysis_:1;
     bool            needsArgsObj_:1;
 
     //
     // End of fields.  Start methods.
     //
 
   public:
-    static JSScript *Create(JSContext *cx, bool savedCallerFun,
+    static JSScript *Create(JSContext *cx, js::HandleObject enclosingScope, bool savedCallerFun,
                             JSPrincipals *principals, JSPrincipals *originPrincipals,
                             bool compileAndGo, bool noScriptRval,
                             JSVersion version, unsigned staticLevel);
 
     // Three ways ways to initialize a JSScript.  Callers of partiallyInit()
     // and fullyInitTrivial() are responsible for notifying the debugger after
     // successfully creating any kind (function or other) of new JSScript.
     // However, callers of fullyInitFromEmitter() do not need to do this.
@@ -609,16 +605,19 @@ struct JSScript : public js::gc::Cell
     inline void clearAnalysis();
     inline js::analyze::ScriptAnalysis *analysis();
 
     inline bool hasGlobal() const;
     inline bool hasClearedGlobal() const;
 
     inline js::GlobalObject &global() const;
 
+    /* See StaticScopeIter comment. */
+    JSObject *enclosingStaticScope() const { return enclosingScope_; }
+
   private:
     bool makeTypes(JSContext *cx);
     bool makeAnalysis(JSContext *cx);
 
 #ifdef JS_METHODJIT
   private:
     // CallCompiler must be a friend because it generates code that directly
     // accesses jitHandleNormal/jitHandleCtor, via jitHandleOffset().
@@ -1002,22 +1001,23 @@ enum LineOption {
     CALLED_FROM_JSOP_EVAL,
     NOT_CALLED_FROM_JSOP_EVAL
 };
 
 inline void
 CurrentScriptFileLineOrigin(JSContext *cx, unsigned *linenop, LineOption = NOT_CALLED_FROM_JSOP_EVAL);
 
 extern JSScript *
-CloneScript(JSContext *cx, HandleScript script);
+CloneScript(JSContext *cx, HandleObject enclosingScope, HandleFunction fun, HandleScript script);
 
 /*
  * NB: after a successful XDR_DECODE, XDRScript callers must do any required
  * subsequent set-up of owning function or script object and then call
  * js_CallNewScriptHook.
  */
 template<XDRMode mode>
 bool
-XDRScript(XDRState<mode> *xdr, JSScript **scriptp, JSScript *parentScript);
+XDRScript(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript enclosingScript,
+          HandleFunction fun, JSScript **scriptp);
 
 } /* namespace js */
 
 #endif /* jsscript_h___ */
--- a/js/src/tests/js1_5/extensions/regress-300079.js
+++ b/js/src/tests/js1_5/extensions/regress-300079.js
@@ -22,17 +22,17 @@ function test()
 
   if (typeof clone == 'undefined') {
     expect = 'SKIPPED';
     actual = 'SKIPPED';
   }
   else {
     expect = 'PASSED';
 
-    f = Function("a", "return (function () { return a * a;});")();
+    f = 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";
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -109,16 +109,17 @@ GlobalObject::initFunctionAndObjectClass
         JSObject *proto = js_NewFunction(cx, functionProto,
                                          NULL, 0, JSFUN_INTERPRETED, self, NULL);
         if (!proto)
             return NULL;
         JS_ASSERT(proto == functionProto);
         functionProto->flags |= JSFUN_PROTOTYPE;
 
         Rooted<JSScript*> script(cx, JSScript::Create(cx,
+                                                      /* enclosingScope = */ NullPtr(),
                                                       /* savedCallerFun = */ false,
                                                       /* principals = */ NULL,
                                                       /* originPrincipals = */ NULL,
                                                       /* compileAndGo = */ false,
                                                       /* noScriptRval = */ true,
                                                       JSVERSION_DEFAULT,
                                                       /* staticLevel = */ 0));
         if (!script || !JSScript::fullyInitTrivial(cx, script))
--- a/js/src/vm/ScopeObject-inl.h
+++ b/js/src/vm/ScopeObject-inl.h
@@ -147,27 +147,46 @@ BlockObject::slotValue(unsigned i)
 
 inline void
 BlockObject::setSlotValue(unsigned i, const Value &v)
 {
     JS_ASSERT(i < slotCount());
     setSlot(RESERVED_SLOTS + i, v);
 }
 
+inline void
+StaticBlockObject::initPrevBlockChainFromParser(StaticBlockObject *prev)
+{
+    setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(prev));
+}
+
+inline void
+StaticBlockObject::resetPrevBlockChainFromParser()
+{
+    setReservedSlot(SCOPE_CHAIN_SLOT, UndefinedValue());
+}
+
+inline void
+StaticBlockObject::initEnclosingStaticScope(JSObject *obj)
+{
+    JS_ASSERT(getReservedSlot(SCOPE_CHAIN_SLOT).isUndefined());
+    setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(obj));
+}
+
 inline StaticBlockObject *
 StaticBlockObject::enclosingBlock() const
 {
     JSObject *obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
-    return obj ? &obj->asStaticBlock() : NULL;
+    return obj && obj->isStaticBlock() ? &obj->asStaticBlock() : NULL;
 }
 
-inline void
-StaticBlockObject::setEnclosingBlock(StaticBlockObject *blockObj)
+inline JSObject *
+StaticBlockObject::enclosingStaticScope() const
 {
-    setFixedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(blockObj));
+    return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
 }
 
 inline void
 StaticBlockObject::setStackDepth(uint32_t depth)
 {
     JS_ASSERT(getReservedSlot(DEPTH_SLOT).isUndefined());
     initReservedSlot(DEPTH_SLOT, PrivateUint32Value(depth));
 }
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -18,16 +18,75 @@
 
 #include "ScopeObject-inl.h"
 
 using namespace js;
 using namespace js::types;
 
 /*****************************************************************************/
 
+StaticScopeIter::StaticScopeIter(JSObject *obj)
+  : obj(obj), onNamedLambda(false)
+{
+    JS_ASSERT_IF(obj, obj->isStaticBlock() || obj->isFunction());
+}
+
+bool
+StaticScopeIter::done() const
+{
+    return obj == NULL;
+}
+
+void
+StaticScopeIter::operator++(int)
+{
+    if (obj->isStaticBlock()) {
+        obj = obj->asStaticBlock().enclosingStaticScope();
+    } else if (onNamedLambda || !obj->toFunction()->isNamedLambda()) {
+        onNamedLambda = false;
+        obj = obj->toFunction()->script()->enclosingStaticScope();
+    } else {
+        onNamedLambda = true;
+    }
+    JS_ASSERT_IF(obj, obj->isStaticBlock() || obj->isFunction());
+    JS_ASSERT_IF(onNamedLambda, obj->isFunction());
+}
+
+bool
+StaticScopeIter::hasDynamicScopeObject() const
+{
+    return obj->isStaticBlock()
+           ? obj->asStaticBlock().needsClone()
+           : obj->toFunction()->isHeavyweight();
+}
+
+StaticScopeIter::Type
+StaticScopeIter::type() const
+{
+    if (onNamedLambda)
+        return NAMED_LAMBDA;
+    return obj->isStaticBlock() ? BLOCK : FUNCTION;
+}
+
+StaticBlockObject &
+StaticScopeIter::block() const
+{
+    JS_ASSERT(type() == BLOCK);
+    return obj->asStaticBlock();
+}
+
+JSScript *
+StaticScopeIter::funScript() const
+{
+    JS_ASSERT(type() == FUNCTION);
+    return obj->toFunction()->script();
+}
+
+/*****************************************************************************/
+
 StaticBlockObject *
 js::ScopeCoordinateBlockChain(JSScript *script, jsbytecode *pc)
 {
     ScopeCoordinate sc(pc);
 
     uint32_t blockIndex = GET_UINT32_INDEX(pc + 2 * sizeof(uint16_t));
     if (blockIndex == UINT32_MAX)
         return NULL;
@@ -135,17 +194,17 @@ CallObject::createForFunction(JSContext 
     JS_ASSERT(fp->isNonEvalFunctionFrame());
 
     RootedObject scopeChain(cx, fp->scopeChain());
 
     /*
      * For a named function expression Call's parent points to an environment
      * object holding function's name.
      */
-    if (js_IsNamedLambda(fp->fun())) {
+    if (fp->fun()->isNamedLambda()) {
         scopeChain = DeclEnvObject::create(cx, fp);
         if (!scopeChain)
             return NULL;
     }
 
     RootedScript script(cx, fp->script());
     Rooted<JSFunction*> callee(cx, &fp->callee());
     CallObject *callobj = create(cx, script, scopeChain, callee);
@@ -676,79 +735,46 @@ Class js::BlockClass = {
     JS_PropertyStub,         /* delProperty */
     JS_PropertyStub,         /* getProperty */
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub
 };
 
-#define NO_PARENT_INDEX UINT32_MAX
-
-/*
- * If there's a parent id, then get the parent out of our script's object
- * array. We know that we clone block objects in outer-to-inner order, which
- * means that getting the parent now will work.
- */
-static uint32_t
-FindObjectIndex(JSScript *script, StaticBlockObject *maybeBlock)
-{
-    if (!maybeBlock || !script->hasObjects())
-        return NO_PARENT_INDEX;
-
-    ObjectArray *objects = script->objects();
-    HeapPtrObject *vector = objects->vector;
-    unsigned length = objects->length;
-    for (unsigned i = 0; i < length; ++i) {
-        if (vector[i] == maybeBlock)
-            return i;
-    }
-
-    return NO_PARENT_INDEX;
-}
-
 template<XDRMode mode>
 bool
-js::XDRStaticBlockObject(XDRState<mode> *xdr, JSScript *script, StaticBlockObject **objp)
+js::XDRStaticBlockObject(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript script,
+                         StaticBlockObject **objp)
 {
     /* NB: Keep this in sync with CloneStaticBlockObject. */
 
     JSContext *cx = xdr->cx();
 
     Rooted<StaticBlockObject*> obj(cx);
-    uint32_t parentId = 0;
     uint32_t count = 0;
     uint32_t depthAndCount = 0;
+
     if (mode == XDR_ENCODE) {
         obj = *objp;
-        parentId = FindObjectIndex(script, obj->enclosingBlock());
         uint32_t depth = obj->stackDepth();
         JS_ASSERT(depth <= UINT16_MAX);
         count = obj->slotCount();
         JS_ASSERT(count <= UINT16_MAX);
         depthAndCount = (depth << 16) | uint16_t(count);
     }
 
-    /* First, XDR the parent atomid. */
-    if (!xdr->codeUint32(&parentId))
-        return false;
-
     if (mode == XDR_DECODE) {
         obj = StaticBlockObject::create(cx);
         if (!obj)
             return false;
+        obj->initEnclosingStaticScope(enclosingScope);
         *objp = obj;
-
-        obj->setEnclosingBlock(parentId == NO_PARENT_INDEX
-                               ? NULL
-                               : &script->getObject(parentId)->asStaticBlock());
     }
 
-    AutoObjectRooter tvr(cx, obj);
-
     if (!xdr->codeUint32(&depthAndCount))
         return false;
 
     if (mode == XDR_DECODE) {
         uint32_t depth = uint16_t(depthAndCount >> 16);
         count = uint16_t(depthAndCount);
         obj->setStackDepth(depth);
 
@@ -813,36 +839,31 @@ js::XDRStaticBlockObject(XDRState<mode> 
             if (!xdr->codeUint32(&aliased))
                 return false;
         }
     }
     return true;
 }
 
 template bool
-js::XDRStaticBlockObject(XDRState<XDR_ENCODE> *xdr, JSScript *script, StaticBlockObject **objp);
+js::XDRStaticBlockObject(XDRState<XDR_ENCODE> *, HandleObject, HandleScript, StaticBlockObject **);
 
 template bool
-js::XDRStaticBlockObject(XDRState<XDR_DECODE> *xdr, JSScript *script, StaticBlockObject **objp);
+js::XDRStaticBlockObject(XDRState<XDR_DECODE> *, HandleObject, HandleScript, StaticBlockObject **);
 
 JSObject *
-js::CloneStaticBlockObject(JSContext *cx, Handle<StaticBlockObject*> srcBlock,
-                           const AutoObjectVector &objects, JSScript *src)
+js::CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle<StaticBlockObject*> srcBlock)
 {
     /* NB: Keep this in sync with XDRStaticBlockObject. */
 
     Rooted<StaticBlockObject*> clone(cx, StaticBlockObject::create(cx));
     if (!clone)
         return NULL;
 
-    uint32_t parentId = FindObjectIndex(src, srcBlock->enclosingBlock());
-    clone->setEnclosingBlock(parentId == NO_PARENT_INDEX
-                             ? NULL
-                             : &objects[parentId]->asStaticBlock());
-
+    clone->initEnclosingStaticScope(enclosingScope);
     clone->setStackDepth(srcBlock->stackDepth());
 
     /* Shape::Range is reverse order, so build a list in forward order. */
     AutoShapeVector shapes(cx);
     if (!shapes.growBy(srcBlock->slotCount()))
         return NULL;
     for (Shape::Range r = srcBlock->lastProperty()->all(); !r.empty(); r.popFront())
         shapes[r.front().shortid()] = &r.front();
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -14,23 +14,80 @@
 
 #include "gc/Barrier.h"
 
 namespace js {
 
 /*****************************************************************************/
 
 /*
+ * All function scripts have an "enclosing static scope" that refers to the
+ * innermost enclosing let or function in the program text. This allows full
+ * reconstruction of the lexical scope for debugging or compiling efficient
+ * access to variables in enclosing scopes. The static scope is represented at
+ * runtime by a tree of compiler-created objects representing each scope:
+ *  - a StaticBlockObject is created for 'let' and 'catch' scopes
+ *  - a JSFunction+JSScript+Bindings trio is created for function scopes
+ * (These objects are primarily used to clone objects scopes for the
+ * dynamic scope chain.)
+ *
+ * There is an additional scope for named lambdas. E.g., in:
+ *
+ *   (function f() { var x; function g() { } })
+ *
+ * g's innermost enclosing scope will first be the function scope containing
+ * 'x', enclosed by a scope containing only the name 'f'. (This separate scope
+ * is necessary due to the fact that declarations in the function scope shadow
+ * (dynamically, in the case of 'eval') the lambda name.)
+ *
+ * There are two limitations to the current lexical nesting information:
+ *
+ *  - 'with' is completely absent; this isn't a problem for the current use
+ *    cases since 'with' causes every static scope to be on the dynamic scope
+ *    chain (so the debugger can find everything) and inhibits all upvar
+ *    optimization.
+ *
+ *  - The "enclosing static scope" chain stops at 'eval'. For example in:
+ *      let (x) { eval("function f() {}") }
+ *    f does not have an enclosing static scope. This is fine for current uses
+ *    for the same reason as 'with'.
+ *
+ * (See also AssertDynamicScopeMatchesStaticScope.)
+ */
+class StaticScopeIter
+{
+    JSObject *obj;
+    bool onNamedLambda;
+
+  public:
+    explicit StaticScopeIter(JSObject *obj);
+
+    bool done() const;
+    void operator++(int);
+
+    /* Return whether this static scope will be on the dynamic scope chain. */
+    bool hasDynamicScopeObject() const;
+
+    enum Type { BLOCK, FUNCTION, NAMED_LAMBDA };
+    Type type() const;
+
+    StaticBlockObject &block() const;
+    JSScript *funScript() const;
+};
+
+/*****************************************************************************/
+
+/*
  * A "scope coordinate" describes how to get from head of the scope chain to a
  * given lexically-enclosing variable. A scope coordinate has two dimensions:
  *  - hops: the number of scope objects on the scope chain to skip
- *  - binding: which binding on the scope object
+ *  - slot: the slot on the scope object holding the variable's value
  * Additionally (as described in jsopcode.tbl) there is a 'block' index, but
  * this is only needed for decompilation/inference so it is not included in the
- * main ScopeCoordinate struct: use ScopeCoordinate{BlockChain,Atom} instead.
+ * main ScopeCoordinate struct: use ScopeCoordinate{BlockChain,Name} instead.
  */
 struct ScopeCoordinate
 {
     uint16_t hops;
     uint16_t slot;
 
     inline ScopeCoordinate(jsbytecode *pc);
     inline ScopeCoordinate() {}
@@ -223,41 +280,65 @@ class BlockObject : public NestedScopeOb
     inline void setSlotValue(unsigned i, const Value &v);
 };
 
 class StaticBlockObject : public BlockObject
 {
   public:
     static StaticBlockObject *create(JSContext *cx);
 
+    /* See StaticScopeIter comment. */
+    inline JSObject *enclosingStaticScope() const;
+
+    /*
+     * A refinement of enclosingStaticScope that returns NULL if the enclosing
+     * static scope is a JSFunction.
+     */
     inline StaticBlockObject *enclosingBlock() const;
-    inline void setEnclosingBlock(StaticBlockObject *blockObj);
+
+    /*
+     * Return whether this StaticBlockObject contains a variable stored at
+     * the given stack depth (i.e., fp->base()[depth]).
+     */
+    bool containsVarAtDepth(uint32_t depth);
 
+    /*
+     * A let binding is aliased if accessed lexically by nested functions or
+     * dynamically through dynamic name lookup (eval, with, function::, etc).
+     */
+    bool isAliased(unsigned i);
+
+    /*
+     * A static block object is cloned (when entering the block) iff some
+     * variable of the block isAliased.
+     */
+    bool needsClone();
+
+    /* Frontend-only functions ***********************************************/
+
+    /* Initialization functions for above fields. */
+    void setAliased(unsigned i, bool aliased);
     void setStackDepth(uint32_t depth);
-    bool containsVarAtDepth(uint32_t depth);
+    void initEnclosingStaticScope(JSObject *obj);
 
     /*
      * Frontend compilation temporarily uses the object's slots to link
      * a let var to its associated Definition parse node.
      */
     void setDefinitionParseNode(unsigned i, Definition *def);
     Definition *maybeDefinitionParseNode(unsigned i);
 
     /*
-     * A let binding is aliased is accessed lexically by nested functions or
-     * dynamically through dynamic name lookup (eval, with, function::, etc).
+     * The parser uses 'enclosingBlock' as the prev-link in the tc->blockChain
+     * stack. Note: in the case of hoisting, this prev-link will not ultimately
+     * be the same as enclosingBlock, initEnclosingStaticScope must be called
+     * separately in the emitter. 'reset' is just for asserting stackiness.
      */
-    void setAliased(unsigned i, bool aliased);
-    bool isAliased(unsigned i);
-
-    /*
-     * A static block object is cloned (when entering the block) iff some
-     * variable of the block isAliased.
-     */
-    bool needsClone();
+    void initPrevBlockChainFromParser(StaticBlockObject *prev);
+    void resetPrevBlockChainFromParser();
 
     static Shape *addVar(JSContext *cx, Handle<StaticBlockObject*> block, HandleId id,
                          int index, bool *redeclared);
 };
 
 class ClonedBlockObject : public BlockObject
 {
   public:
@@ -272,21 +353,21 @@ class ClonedBlockObject : public BlockOb
     void setVar(unsigned i, const Value &v, MaybeCheckAliasing = CHECK_ALIASING);
 
     /* Copy in all the unaliased formals and locals. */
     void copyUnaliasedValues(StackFrame *fp);
 };
 
 template<XDRMode mode>
 bool
-XDRStaticBlockObject(XDRState<mode> *xdr, JSScript *script, StaticBlockObject **objp);
+XDRStaticBlockObject(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript script,
+                     StaticBlockObject **objp);
 
 extern JSObject *
-CloneStaticBlockObject(JSContext *cx, Handle<StaticBlockObject*> srcBlock,
-                       const AutoObjectVector &objects, JSScript *src);
+CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle<StaticBlockObject*> src);
 
 /*****************************************************************************/
 
 class ScopeIterKey;
 
 /*
  * A scope iterator describes the active scopes enclosing the current point of
  * execution for a single frame, proceeding from inner to outer. Here, "frame"
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -217,16 +217,52 @@ StackFrame::pcQuadratic(const ContextSta
      */
     if (StackFrame *next = seg.computeNextFrame(this, maxDepth))
         return next->prevpc();
 
     /* If we hit the limit, just return the beginning of the script. */
     return regs.fp()->script()->code;
 }
 
+static inline void
+AssertDynamicScopeMatchesStaticScope(JSScript *script, JSObject *scope)
+{
+#ifdef DEBUG
+    for (StaticScopeIter i(script->enclosingStaticScope()); !i.done(); i++) {
+        if (i.hasDynamicScopeObject()) {
+            /*
+             * 'with' does not participate in the static scope of the script,
+             * but it does in the dynamic scope, so skip them here.
+             */
+            while (scope->isWith())
+                scope = &scope->asWith().enclosingScope();
+
+            switch (i.type()) {
+              case StaticScopeIter::BLOCK:
+                JS_ASSERT(i.block() == scope->asClonedBlock().staticBlock());
+                scope = &scope->asClonedBlock().enclosingScope();
+                break;
+              case StaticScopeIter::FUNCTION:
+                JS_ASSERT(i.funScript() == scope->asCall().callee().script());
+                scope = &scope->asCall().enclosingScope();
+                break;
+              case StaticScopeIter::NAMED_LAMBDA:
+                scope = &scope->asDeclEnv().enclosingScope();
+                break;
+            }
+        }
+    }
+
+    /*
+     * Ideally, we'd JS_ASSERT(!scope->isScope()) but the enclosing lexical
+     * scope chain stops at eval() boundaries. See StaticScopeIter comment.
+     */
+#endif
+}
+
 bool
 StackFrame::prologue(JSContext *cx, bool newType)
 {
     JS_ASSERT(!isDummyFrame());
     JS_ASSERT(!isGeneratorFrame());
     JS_ASSERT(cx->regs().pc == script()->code);
 
     if (isEvalFrame()) {
@@ -242,16 +278,17 @@ StackFrame::prologue(JSContext *cx, bool
     }
 
     if (isGlobalFrame()) {
         Probes::enterScript(cx, script(), NULL, this);
         return true;
     }
 
     JS_ASSERT(isNonEvalFunctionFrame());
+    AssertDynamicScopeMatchesStaticScope(script(), scopeChain());
 
     if (fun()->isHeavyweight()) {
         CallObject *callobj = CallObject::createForFunction(cx, this);
         if (!callobj)
             return false;
         pushOnScopeChain(*callobj);
         flags_ |= HAS_CALL_OBJ;
     }
@@ -294,22 +331,21 @@ StackFrame::epilogue(JSContext *cx)
     }
 
     if (isGlobalFrame()) {
         JS_ASSERT(!scopeChain()->isScope());
         return;
     }
 
     JS_ASSERT(isNonEvalFunctionFrame());
-    if (fun()->isHeavyweight()) {
+
+    if (fun()->isHeavyweight())
         JS_ASSERT_IF(hasCallObj(), scopeChain()->asCall().callee().script() == script());
-    } else {
-        JS_ASSERT(!scopeChain()->isCall() || scopeChain()->asCall().isForEval() ||
-                  scopeChain()->asCall().callee().script() != script());
-    }
+    else
+        AssertDynamicScopeMatchesStaticScope(script(), scopeChain());
 
     if (cx->compartment->debugMode())
         cx->runtime->debugScopes->onPopCall(this, cx);
 
 
     if (isConstructing() && returnValue().isPrimitive())
         setReturnValue(ObjectValue(constructorThis()));
 }
--- a/js/src/vm/Xdr.cpp
+++ b/js/src/vm/Xdr.cpp
@@ -118,32 +118,38 @@ VersionCheck(XDRState<mode> *xdr)
 
 template<XDRMode mode>
 bool
 XDRState<mode>::codeFunction(JSObject **objp)
 {
     if (mode == XDR_DECODE)
         *objp = NULL;
 
-    return VersionCheck(this) && XDRInterpretedFunction(this, objp, NULL);
+    if (!VersionCheck(this))
+        return false;
+
+    return XDRInterpretedFunction(this, NullPtr(), NullPtr(), objp);
 }
 
 template<XDRMode mode>
 bool
 XDRState<mode>::codeScript(JSScript **scriptp)
 {
     JSScript *script;
     if (mode == XDR_DECODE) {
         script = NULL;
         *scriptp = NULL;
     } else {
         script = *scriptp;
     }
 
-    if (!VersionCheck(this) || !XDRScript(this, &script, NULL))
+    if (!VersionCheck(this))
+        return false;
+
+    if (!XDRScript(this, NullPtr(), NullPtr(), NullPtr(), &script))
         return false;
 
     if (mode == XDR_DECODE) {
         JS_ASSERT(!script->compileAndGo);
         js_CallNewScriptHook(cx(), script, NULL);
         Debugger::onNewScript(cx(), script, NULL);
         *scriptp = script;
     }
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -20,17 +20,17 @@ namespace js {
  * Bytecode version number. Increment the subtrahend whenever JS bytecode
  * changes incompatibly.
  *
  * This version number is XDR'd near the front of xdr bytecode and
  * aborts deserialization if there is a mismatch between the current
  * and saved versions. If deserialization fails, the data should be
  * invalidated if possible.
  */
-static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 119);
+static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 120);
 
 class XDRBuffer {
   public:
     XDRBuffer(JSContext *cx)
       : context(cx), base(NULL), cursor(NULL), limit(NULL) { }
 
     JSContext *cx() const {
         return context;