Bug 740259 - Assert that dynamic binding access is expected (r=bhackett)
authorLuke Wagner <luke@mozilla.com>
Fri, 16 Mar 2012 12:02:37 -0700
changeset 94670 4c298ca28fa6580ecdeeb72964ff592ba4cecdbb
parent 94669 3bc6b34d23daf6df7f38f2fe48c6388d408454ea
child 94671 fa24b215d49e781ccee44780c34230a3b524de5b
push idunknown
push userunknown
push dateunknown
reviewersbhackett
bugs740259
milestone14.0a1
Bug 740259 - Assert that dynamic binding access is expected (r=bhackett)
js/src/frontend/BytecodeEmitter-inl.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/ParseMaps.h
js/src/frontend/Parser.cpp
js/src/jit-test/tests/basic/testDynamicLookup.js
js/src/jit-test/tests/basic/testFunctionStatementAliasLocals.js
js/src/jit-test/tests/basic/testWeirdThingsInFunctionConstructor.js
js/src/jsanalyze.cpp
js/src/jsdbgapi.cpp
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsfuninlines.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/vm/ArgumentsObject.cpp
js/src/vm/ScopeObject-inl.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
--- a/js/src/frontend/BytecodeEmitter-inl.h
+++ b/js/src/frontend/BytecodeEmitter-inl.h
@@ -47,27 +47,29 @@
 namespace js {
 
 inline
 TreeContext::TreeContext(Parser *prs)
   : flags(0), bodyid(0), blockidGen(0), parenDepth(0), yieldCount(0), argumentsCount(0),
     topStmt(NULL), topScopeStmt(NULL), blockChain(NULL), blockNode(NULL),
     decls(prs->context), parser(prs), yieldNode(NULL), argumentsNode(NULL), scopeChain_(NULL),
     lexdeps(prs->context), parent(prs->tc), staticLevel(0), funbox(NULL), functionList(NULL),
-    innermostWith(NULL), bindings(prs->context), bindingsRoot(prs->context, &bindings)
+    innermostWith(NULL), bindings(prs->context), bindingsRoot(prs->context, &bindings),
+    funcStmts(NULL)
 {
     prs->tc = this;
 }
 
 /*
  * For functions the tree context is constructed and destructed a second
  * time during code generation. To avoid a redundant stats update in such
  * cases, we store UINT16_MAX in maxScopeDepth.
  */
 inline
 TreeContext::~TreeContext()
 {
     parser->tc = this->parent;
+    parser->context->delete_(funcStmts);
 }
 
 } /* namespace js */
 
 #endif /* BytecodeEmitter_inl_h__ */
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -973,17 +973,17 @@ EmitArguments(JSContext *cx, BytecodeEmi
     if (!bce->mayOverwriteArguments())
         return Emit1(cx, bce, JSOP_ARGUMENTS) >= 0;
     return EmitAtomOp(cx, cx->runtime->atomState.argumentsAtom, JSOP_NAME, bce);
 }
 
 bool
 BytecodeEmitter::shouldNoteClosedName(ParseNode *pn)
 {
-    return !callsEval() && pn->isDefn() && pn->isClosed();
+    return !bindingsAccessedDynamically() && pn->isDefn() && pn->isClosed();
 }
 
 bool
 BytecodeEmitter::noteClosedVar(ParseNode *pn)
 {
 #ifdef DEBUG
     JS_ASSERT(shouldNoteClosedName(pn));
     Definition *dn = (Definition *)pn;
@@ -1051,17 +1051,16 @@ EmitEnterBlock(JSContext *cx, BytecodeEm
     blockObj.setStackDepth(depth);
 
     int depthPlusFixed = AdjustBlockSlot(cx, bce, depth);
     if (depthPlusFixed < 0)
         return false;
 
     for (unsigned i = 0; i < blockObj.slotCount(); i++) {
         Definition *dn = blockObj.maybeDefinitionParseNode(i);
-        blockObj.poisonDefinitionParseNode(i);
 
         /* Beware the empty destructuring dummy. */
         if (!dn) {
             JS_ASSERT(i + 1 <= blockObj.slotCount());
             continue;
         }
 
         JS_ASSERT(dn->isDefn());
@@ -1069,16 +1068,19 @@ EmitEnterBlock(JSContext *cx, BytecodeEm
         dn->pn_cookie.set(dn->pn_cookie.level(), uint16_t(dn->frameSlot() + depthPlusFixed));
 #ifdef DEBUG
         for (ParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) {
             JS_ASSERT(pnu->pn_lexdef == dn);
             JS_ASSERT(!(pnu->pn_dflags & PND_BOUND));
             JS_ASSERT(pnu->pn_cookie.isFree());
         }
 #endif
+
+        bool aliased = bce->bindingsAccessedDynamically() || bce->shouldNoteClosedName(dn);
+        blockObj.setAliased(i, aliased);
     }
 
     /*
      * If clones of this block will have any extensible parents, then the
      * clones must get unique shapes; see the comments for
      * js::Bindings::extensibleParents.
      */
     if ((bce->flags & TCF_FUN_EXTENSIBLE_SCOPE) ||
@@ -3262,36 +3264,41 @@ EmitGroupAssignment(JSContext *cx, Bytec
     }
 
     nslots = limit - depth;
     EMIT_UINT16_IMM_OP(JSOP_POPN, nslots);
     bce->stackDepth = (unsigned) depth;
     return JS_TRUE;
 }
 
+enum GroupOption { GroupIsDecl, GroupIsNotDecl };
+
 /*
  * Helper called with pop out param initialized to a JSOP_POP* opcode.  If we
  * can emit a group assignment sequence, which results in 0 stack depth delta,
  * we set *pop to JSOP_NOP so callers can veto emitting pn followed by a pop.
  */
-static JSBool
+static bool
 MaybeEmitGroupAssignment(JSContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn,
-                         JSOp *pop)
+                         GroupOption groupOption, JSOp *pop)
 {
     JS_ASSERT(pn->isKind(PNK_ASSIGN));
     JS_ASSERT(pn->isOp(JSOP_NOP));
     JS_ASSERT(*pop == JSOP_POP || *pop == JSOP_POPV);
 
     ParseNode *lhs = pn->pn_left;
     ParseNode *rhs = pn->pn_right;
     if (lhs->isKind(PNK_RB) && rhs->isKind(PNK_RB) &&
         !(rhs->pn_xflags & PNX_HOLEY) &&
-        lhs->pn_count <= rhs->pn_count) {
+        lhs->pn_count <= rhs->pn_count)
+    {
+        if (groupOption == GroupIsDecl && !EmitDestructuringDecls(cx, bce, prologOp, lhs))
+            return false;
         if (!EmitGroupAssignment(cx, bce, prologOp, lhs, rhs))
-            return JS_FALSE;
+            return false;
         *pop = JSOP_NOP;
     }
     return JS_TRUE;
 }
 
 /*
  * Like MaybeEmitGroupAssignment, but for 'let ([x,y] = [a,b]) ...'.
  *
@@ -3404,17 +3411,17 @@ EmitVariables(JSContext *cx, BytecodeEmi
                  * head, pass JSOP_POP rather than the pseudo-prolog JSOP_NOP
                  * in pn->pn_op, to suppress a second (and misplaced) 'let'.
                  */
                 JS_ASSERT(noteIndex < 0 && !pn2->pn_next);
                 if (letNotes) {
                     if (!MaybeEmitLetGroupDecl(cx, bce, pn2, letNotes, &op))
                         return JS_FALSE;
                 } else {
-                    if (!MaybeEmitGroupAssignment(cx, bce, pn->getOp(), pn2, &op))
+                    if (!MaybeEmitGroupAssignment(cx, bce, pn->getOp(), pn2, GroupIsDecl, &op))
                         return JS_FALSE;
                 }
             }
             if (op == JSOP_NOP) {
                 pn->pn_xflags = (pn->pn_xflags & ~PNX_POPVAR) | PNX_GROUPINIT;
             } else {
                 pn3 = pn2->pn_left;
                 if (!EmitDestructuringDecls(cx, bce, pn->getOp(), pn3))
@@ -4783,17 +4790,17 @@ EmitNormalFor(JSContext *cx, BytecodeEmi
     if (!pn3) {
         /* No initializer: emit an annotated nop for the decompiler. */
         op = JSOP_NOP;
     } else {
         bce->flags |= TCF_IN_FOR_INIT;
 #if JS_HAS_DESTRUCTURING
         if (pn3->isKind(PNK_ASSIGN)) {
             JS_ASSERT(pn3->isOp(JSOP_NOP));
-            if (!MaybeEmitGroupAssignment(cx, bce, op, pn3, &op))
+            if (!MaybeEmitGroupAssignment(cx, bce, op, pn3, GroupIsNotDecl, &op))
                 return false;
         }
 #endif
         if (op == JSOP_POP) {
             if (!EmitTree(cx, bce, pn3))
                 return false;
             if (pn3->isKind(PNK_VAR) || pn3->isKind(PNK_CONST) || pn3->isKind(PNK_LET)) {
                 /*
@@ -4855,17 +4862,17 @@ EmitNormalFor(JSContext *cx, BytecodeEmi
 
     /* Check for update code to do before the condition (if any). */
     pn3 = forHead->pn_kid3;
     if (pn3) {
         op = JSOP_POP;
 #if JS_HAS_DESTRUCTURING
         if (pn3->isKind(PNK_ASSIGN)) {
             JS_ASSERT(pn3->isOp(JSOP_NOP));
-            if (!MaybeEmitGroupAssignment(cx, bce, op, pn3, &op))
+            if (!MaybeEmitGroupAssignment(cx, bce, op, pn3, GroupIsNotDecl, &op))
                 return false;
         }
 #endif
         if (op == JSOP_POP && !EmitTree(cx, bce, pn3))
             return false;
 
         /* Always emit the POP or NOP, to help the decompiler. */
         if (Emit1(cx, bce, op) < 0)
@@ -5340,17 +5347,17 @@ EmitStatement(JSContext *cx, BytecodeEmi
     }
 
     if (useful) {
         JSOp op = wantval ? JSOP_POPV : JSOP_POP;
         JS_ASSERT_IF(pn2->isKind(PNK_ASSIGN), pn2->isOp(JSOP_NOP));
 #if JS_HAS_DESTRUCTURING
         if (!wantval &&
             pn2->isKind(PNK_ASSIGN) &&
-            !MaybeEmitGroupAssignment(cx, bce, op, pn2, &op))
+            !MaybeEmitGroupAssignment(cx, bce, op, pn2, GroupIsNotDecl, &op))
         {
             return false;
         }
 #endif
         if (op != JSOP_NOP) {
             if (!EmitTree(cx, bce, pn2))
                 return false;
             if (Emit1(cx, bce, op) < 0)
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -51,16 +51,18 @@
 #include "jsprvtd.h"
 #include "jspubtd.h"
 
 #include "frontend/Parser.h"
 #include "frontend/ParseMaps.h"
 
 namespace js {
 
+typedef HashSet<JSAtom *> FuncStmtSet;
+
 /*
  * NB: If you add enumerators for scope statements, add them between STMT_WITH
  * and STMT_CATCH, or you will break the STMT_TYPE_IS_SCOPE macro. If you add
  * non-looping statement enumerators, add them before STMT_DO_LOOP or you will
  * break the STMT_TYPE_IS_LOOP macro.
  *
  * Also remember to keep the statementName array in BytecodeEmitter.cpp in
  * sync.
@@ -224,18 +226,39 @@ JS_ENUM_HEADER(TreeContextFlags, uint32_
     /*
      * This function/global/eval code body contained a Use Strict Directive.
      * Treat certain strict warnings as errors, and forbid the use of 'with'.
      * See also TSF_STRICT_MODE_CODE, JSScript::strictModeCode, and
      * JSREPORT_STRICT_ERROR.
      */
     TCF_STRICT_MODE_CODE =                 0x10000,
 
-    /* The function calls 'eval'. */
-    TCF_FUN_CALLS_EVAL =                   0x20000,
+    /*
+     * The (static) bindings of this script need to support dynamic name
+     * read/write access. Here, 'dynamic' means dynamic dictionary lookup on
+     * the scope chain for a dynamic set of keys. The primary examples are:
+     *  - direct eval
+     *  - function::
+     *  - with
+     * since both effectively allow any name to be accessed. Non-exmaples are:
+     *  - upvars of nested functions
+     *  - function statement
+     * since the set of assigned name is known dynamically. 'with' could be in
+     * the non-example category, provided the set of all free variables within
+     * the with block was noted. However, we do not optimize 'with' so, for
+     * simplicity, 'with' is treated like eval.
+     *
+     * Note: access through the arguments object is not considered dynamic
+     * binding access since it does not go through the normal name lookup
+     * mechanism. This is debatable and could be changed (although care must be
+     * taken not to turn off the whole 'arguments' optimization). To answer the
+     * more general "is this argument aliased" question, script->needsArgsObj
+     * should be tested (see JSScript::argIsAlised).
+     */
+    TCF_BINDINGS_ACCESSED_DYNAMICALLY =    0x20000,
 
     /* The function mutates a positional (non-destructuring) parameter. */
     TCF_FUN_MUTATES_PARAMETER =            0x40000,
 
     /* Compiling an eval() script. */
     TCF_COMPILE_FOR_EVAL =                0x100000,
 
     /*
@@ -272,17 +295,17 @@ static const uint32_t TCF_RETURN_FLAGS =
  * Sticky deoptimization flags to propagate from FunctionBody.
  */
 static const uint32_t TCF_FUN_FLAGS = TCF_FUN_USES_ARGUMENTS |
                                       TCF_FUN_PARAM_ARGUMENTS |
                                       TCF_FUN_LOCAL_ARGUMENTS |
                                       TCF_FUN_HEAVYWEIGHT |
                                       TCF_FUN_IS_GENERATOR |
                                       TCF_FUN_USES_OWN_NAME |
-                                      TCF_FUN_CALLS_EVAL |
+                                      TCF_BINDINGS_ACCESSED_DYNAMICALLY |
                                       TCF_FUN_MIGHT_ALIAS_LOCALS |
                                       TCF_FUN_MUTATES_PARAMETER |
                                       TCF_STRICT_MODE_CODE |
                                       TCF_FUN_EXTENSIBLE_SCOPE;
 
 struct BytecodeEmitter;
 
 struct TreeContext {                /* tree context for semantic checks */
@@ -346,16 +369,20 @@ struct TreeContext {                /* t
     FunctionBox     *functionList;
 
     ParseNode       *innermostWith; /* innermost WITH parse node */
 
     Bindings        bindings;       /* bindings in this code, including
                                        arguments if we're compiling a function */
     Bindings::StackRoot bindingsRoot; /* root for stack allocated bindings. */
 
+    FuncStmtSet *funcStmts;         /* Set of (non-top-level) function statements
+                                       that will alias any top-level bindings with
+                                       the same name. */
+
     void trace(JSTracer *trc);
 
     inline TreeContext(Parser *prs);
     inline ~TreeContext();
 
     /*
      * js::BytecodeEmitter derives from js::TreeContext; however, only the
      * top-level BytecodeEmitters are actually used as full-fledged tree contexts
@@ -404,22 +431,22 @@ struct TreeContext {                /* t
 
     bool compiling() const { return flags & TCF_COMPILING; }
     inline BytecodeEmitter *asBytecodeEmitter();
 
     bool usesArguments() const {
         return flags & TCF_FUN_USES_ARGUMENTS;
     }
 
-    void noteCallsEval() {
-        flags |= TCF_FUN_CALLS_EVAL;
+    void noteBindingsAccessedDynamically() {
+        flags |= TCF_BINDINGS_ACCESSED_DYNAMICALLY;
     }
 
-    bool callsEval() const {
-        return flags & TCF_FUN_CALLS_EVAL;
+    bool bindingsAccessedDynamically() const {
+        return flags & TCF_BINDINGS_ACCESSED_DYNAMICALLY;
     }
 
     void noteMightAliasLocals() {
         flags |= TCF_FUN_MIGHT_ALIAS_LOCALS;
     }
 
     bool mightAliasLocals() const {
         return flags & TCF_FUN_MIGHT_ALIAS_LOCALS;
@@ -435,17 +462,17 @@ struct TreeContext {                /* t
         return flags & TCF_FUN_MUTATES_PARAMETER;
     }
 
     bool mayOverwriteArguments() const {
         JS_ASSERT(inFunction());
         JS_ASSERT_IF(inStrictMode(),
                      !(flags & (TCF_FUN_PARAM_ARGUMENTS | TCF_FUN_LOCAL_ARGUMENTS)));
         return !inStrictMode() &&
-               (callsEval() ||
+               (bindingsAccessedDynamically() ||
                 flags & (TCF_FUN_PARAM_ARGUMENTS | TCF_FUN_LOCAL_ARGUMENTS));
     }
 
     void noteLocalOverwritesArguments() {
         flags |= TCF_FUN_LOCAL_ARGUMENTS;
     }
 
     void noteArgumentsNameUse(ParseNode *node) {
@@ -464,17 +491,18 @@ struct TreeContext {                /* t
     void countArgumentsUse(ParseNode *node) {
         JS_ASSERT(node->isKind(PNK_NAME));
         JS_ASSERT(node->pn_atom == parser->context->runtime->atomState.argumentsAtom);
         argumentsCount++;
         argumentsNode = node;
     }
 
     bool needsEagerArguments() const {
-        return inStrictMode() && ((usesArguments() && mutatesParameter()) || callsEval());
+        return inStrictMode() &&
+               (bindingsAccessedDynamically() || (usesArguments() && mutatesParameter()));
     }
 
     void noteHasExtensibleScope() {
         flags |= TCF_FUN_EXTENSIBLE_SCOPE;
     }
 
     bool hasExtensibleScope() const {
         return flags & TCF_FUN_EXTENSIBLE_SCOPE;
--- a/js/src/frontend/ParseMaps.h
+++ b/js/src/frontend/ParseMaps.h
@@ -387,17 +387,17 @@ class MultiDeclRange
 class AtomDeclsIter
 {
     AtomDOHMap::Range   r;     /* Range over the map. */
     AtomDeclNode        *link; /* Optional next node in the current atom's chain. */
 
   public:
     explicit AtomDeclsIter(AtomDecls *decls) : r(decls->all()), link(NULL) {}
 
-    Definition *operator()() {
+    Definition *next() {
         if (link) {
             JS_ASSERT(link != link->next);
             Definition *result = link->defn;
             link = link->next;
             JS_ASSERT(result);
             return result;
         }
 
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -620,16 +620,44 @@ Parser::functionBody(FunctionBodyType ty
 
         /* Check for falling off the end of a function that returns a value. */
         if (context->hasStrictOption() && (tc->flags & TCF_RETURN_EXPR) &&
             !CheckFinalReturn(context, tc, pn)) {
             pn = NULL;
         }
     }
 
+    /*
+     * Check whether any parameters have been assigned within this function.
+     * In strict mode parameters do not alias arguments[i], and to make the
+     * arguments object reflect initial parameter values prior to any mutation
+     * we create it eagerly whenever parameters are (or might, in the case of
+     * calls to eval) be assigned.
+     */
+    if (tc->inStrictMode() && tc->fun()->nargs > 0) {
+        AtomDeclsIter iter(&tc->decls);
+        while (Definition *dn = iter.next()) {
+            if (dn->kind() == Definition::ARG && dn->isAssigned()) {
+                tc->flags |= TCF_FUN_MUTATES_PARAMETER;
+                break;
+            }
+        }
+    }
+
+    /*
+     * Non-top-level functions use JSOP_DEFFUN which is a dynamic scope
+     * operation which means it aliases any bindings with the same name.
+     */
+    if (FuncStmtSet *set = tc->funcStmts) {
+        for (FuncStmtSet::Range r = set->all(); !r.empty(); r.popFront()) {
+            if (Definition *dn = tc->decls.lookupFirst(r.front()))
+                dn->pn_dflags |= PND_CLOSED;
+        }
+    }
+
     tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS);
     return pn;
 }
 
 /* Create a placeholder Definition node for |atom|. */
 static Definition *
 MakePlaceholder(ParseNode *pn, TreeContext *tc)
 {
@@ -1027,16 +1055,22 @@ DeoptimizeUsesWithin(Definition *dn, con
             pnu->pn_dflags |= PND_DEOPTIMIZED;
             ++ndeoptimized;
         }
     }
 
     return ndeoptimized != 0;
 }
 
+/*
+ * Beware: this function is called for functions nested in other functions or
+ * global scripts but not for functions compiled through the Function
+ * constructor or JSAPI. To always execute code when a function has finished
+ * parsing, use Parser::functionBody.
+ */
 static bool
 LeaveFunction(ParseNode *fn, TreeContext *funtc, PropertyName *funName = NULL,
               FunctionSyntaxKind kind = Expression)
 {
     TreeContext *tc = funtc->parent;
     tc->blockidGen = funtc->blockidGen;
 
     FunctionBox *funbox = fn->pn_funbox;
@@ -1068,19 +1102,20 @@ LeaveFunction(ParseNode *fn, TreeContext
                 foundCallee = 1;
                 continue;
             }
 
             Definition *outer_dn = tc->decls.lookupFirst(atom);
 
             /*
              * Make sure to deoptimize lexical dependencies that are polluted
-             * by eval or with, to safely bind globals (see bug 561923).
+             * by eval (approximated by bindingsAccessedDynamically) or with, to
+             * safely bind globals (see bug 561923).
              */
-            if (funtc->callsEval() ||
+            if (funtc->bindingsAccessedDynamically() ||
                 (outer_dn && tc->innermostWith &&
                  outer_dn->pn_pos < tc->innermostWith->pn_pos)) {
                 DeoptimizeUsesWithin(dn, fn->pn_pos);
             }
 
             if (!outer_dn) {
                 AtomDefnAddPtr p = tc->lexdeps->lookupForAdd(atom);
                 if (p) {
@@ -1168,35 +1203,16 @@ LeaveFunction(ParseNode *fn, TreeContext
             funtc->lexdeps.clearMap();
             fn->pn_body->pn_tree = body;
         } else {
             funtc->lexdeps.releaseMap(funtc->parser->context);
         }
 
     }
 
-    /*
-     * Check whether any parameters have been assigned within this function.
-     * In strict mode parameters do not alias arguments[i], and to make the
-     * arguments object reflect initial parameter values prior to any mutation
-     * we create it eagerly whenever parameters are (or might, in the case of
-     * calls to eval) be assigned.
-     */
-    if (funtc->inStrictMode() && funbox->object->toFunction()->nargs > 0) {
-        AtomDeclsIter iter(&funtc->decls);
-        Definition *dn;
-
-        while ((dn = iter()) != NULL) {
-            if (dn->kind() == Definition::ARG && dn->isAssigned()) {
-                funbox->tcflags |= TCF_FUN_MUTATES_PARAMETER;
-                break;
-            }
-        }
-    }
-
     funbox->bindings.transfer(funtc->parser->context, &funtc->bindings);
 
     return true;
 }
 
 static bool
 DefineGlobal(ParseNode *pn, BytecodeEmitter *bce, PropertyName *name);
 
@@ -1482,18 +1498,17 @@ Parser::functionDef(PropertyName *funNam
      * local variables now that we've parsed all the regular and destructuring
      * formal parameters. Because js::Bindings::add must be called first for
      * all ARGUMENTs, then all VARIABLEs and CONSTANTs, and finally all UPVARs,
      * we can't bind vars induced by formal parameter destructuring until after
      * Parser::functionArguments has returned.
      */
     if (prelude) {
         AtomDeclsIter iter(&funtc.decls);
-
-        while (Definition *apn = iter()) {
+        while (Definition *apn = iter.next()) {
             /* Filter based on pn_op -- see BindDestructuringArg, above. */
             if (!apn->isOp(JSOP_SETLOCAL))
                 continue;
 
             if (!BindLocalVariable(context, &funtc, apn, VARIABLE))
                 return NULL;
         }
     }
@@ -1537,30 +1552,23 @@ Parser::functionDef(PropertyName *funNam
     else if (kind == Statement && !MatchOrInsertSemicolon(context, &tokenStream))
         return NULL;
 #else
     MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_BODY);
 #endif
     pn->pn_pos.end = tokenStream.currentToken().pos.end;
 
     /*
-     * Fruit of the poisonous tree: if a closure calls eval, we consider the
-     * parent to call eval. We need this for two reasons: (1) the Jaegermonkey
-     * optimizations really need to know if eval is called transitively, and
-     * (2) in strict mode, eval called transitively requires eager argument
-     * creation in strict mode parent functions.
-     *
-     * For the latter, we really only need to propagate callsEval if both
-     * functions are strict mode, but we don't lose much by always propagating.
-     * The only optimization we lose this way is in the case where a function
-     * is strict, does not mutate arguments, does not call eval directly, but
-     * calls eval transitively.
+     * Fruit of the poisonous tree: if a closure contains a dynamic name access
+     * (eval, with, etc), we consider the parent to do the same. The reason is
+     * that the deoptimizing effects of dynamic name access apply equally to
+     * parents: any local can be read at runtime.
      */
-    if (funtc.callsEval())
-        outertc->noteCallsEval();
+    if (funtc.bindingsAccessedDynamically())
+        outertc->noteBindingsAccessedDynamically();
 
 #if JS_HAS_DESTRUCTURING
     /*
      * If there were destructuring formal parameters, prepend the initializing
      * comma expression that we synthesized to body. If the body is a return
      * node, we must make a special PNK_SEQ node, to prepend the destructuring
      * code without bracing the decompilation of the function body.
      */
@@ -1616,16 +1624,30 @@ Parser::functionDef(PropertyName *funNam
              */
             JS_ASSERT(!outertc->inStrictMode());
             op = JSOP_DEFFUN;
             outertc->noteMightAliasLocals();
             outertc->noteHasExtensibleScope();
             outertc->flags |= TCF_FUN_HEAVYWEIGHT;
             if (fun->atom == context->runtime->atomState.argumentsAtom)
                 outertc->noteLocalOverwritesArguments();
+
+            /*
+             * Instead of noteBindingsAccessedDynamically, which would be
+             * overly conservative, remember the names of all function
+             * statements and mark any bindings with the same as aliased at the
+             * end of functionBody.
+             */
+            if (!outertc->funcStmts) {
+                outertc->funcStmts = context->new_<FuncStmtSet>(context);
+                if (!outertc->funcStmts || !outertc->funcStmts->init())
+                    return NULL;
+            }
+            if (!outertc->funcStmts->put(funName))
+                return NULL;
         }
     }
 
     funbox->kids = funtc.functionList;
 
     pn->pn_funbox = funbox;
     pn->setOp(op);
     if (pn->pn_body) {
@@ -3690,16 +3712,18 @@ Parser::withStatement()
     PushStatement(tc, &stmtInfo, STMT_WITH, -1);
     pn2 = statement();
     if (!pn2)
         return NULL;
     PopStatement(tc);
 
     pn->pn_pos.end = pn2->pn_pos.end;
     pn->pn_right = pn2;
+
+    tc->noteBindingsAccessedDynamically();
     tc->flags |= TCF_FUN_HEAVYWEIGHT;
     tc->innermostWith = oldWith;
 
     /*
      * Make sure to deoptimize lexical dependencies inside the |with|
      * to safely optimize binding globals (see bug 561923).
      */
     for (AtomDefnRange r = tc->lexdeps->all(); !r.empty(); r.popFront()) {
@@ -5731,16 +5755,17 @@ Parser::memberExpr(JSBool allowCallSynta
                 }
             }
 #if JS_HAS_XML_SUPPORT
             else if (!tc->inStrictMode()) {
                 TokenPtr begin = lhs->pn_pos.begin;
                 if (tt == TOK_LP) {
                     /* Filters are effectively 'with', so deoptimize names. */
                     tc->flags |= TCF_FUN_HEAVYWEIGHT;
+                    tc->noteBindingsAccessedDynamically();
 
                     StmtInfo stmtInfo;
                     ParseNode *oldWith = tc->innermostWith;
                     tc->innermostWith = lhs;
                     PushStatement(tc, &stmtInfo, STMT_WITH, -1);
 
                     ParseNode *filter = bracketedExpr();
                     if (!filter)
@@ -5849,17 +5874,17 @@ Parser::memberExpr(JSBool allowCallSynta
             if (!nextMember)
                 return NULL;
             nextMember->setOp(JSOP_CALL);
 
             if (lhs->isOp(JSOP_NAME)) {
                 if (lhs->pn_atom == context->runtime->atomState.evalAtom) {
                     /* Select JSOP_EVAL and flag tc as heavyweight. */
                     nextMember->setOp(JSOP_EVAL);
-                    tc->noteCallsEval();
+                    tc->noteBindingsAccessedDynamically();
                     tc->flags |= TCF_FUN_HEAVYWEIGHT;
                     /*
                      * In non-strict mode code, direct calls to eval can add
                      * variables to the call object.
                      */
                     if (!tc->inStrictMode())
                         tc->noteHasExtensibleScope();
                 }
@@ -6008,19 +6033,18 @@ Parser::qualifiedSuffix(ParseNode *pn)
 {
     JS_ASSERT(!tc->inStrictMode());
 
     JS_ASSERT(tokenStream.currentToken().type == TOK_DBLCOLON);
     ParseNode *pn2 = NameNode::create(PNK_DBLCOLON, NULL, tc);
     if (!pn2)
         return NULL;
 
-    /* This qualifiedSuffice may refer to 'arguments'. */
     tc->flags |= TCF_FUN_HEAVYWEIGHT;
-    tc->noteLocalOverwritesArguments();
+    tc->noteBindingsAccessedDynamically();
 
     /* Left operand of :: must be evaluated if it is an identifier. */
     if (pn->isOp(JSOP_QNAMEPART))
         pn->setOp(JSOP_NAME);
 
     TokenKind tt = tokenStream.getToken(TSF_KEYWORD_IS_NAME);
     if (tt == TOK_STAR || tt == TOK_NAME) {
         /* Inline and specialize propertySelector for JSOP_QNAMECONST. */
@@ -6057,17 +6081,17 @@ Parser::qualifiedIdentifier()
     JS_ASSERT(!tc->inStrictMode());
 
     ParseNode *pn = propertySelector();
     if (!pn)
         return NULL;
     if (tokenStream.matchToken(TOK_DBLCOLON)) {
         /* Hack for bug 496316. Slowing down E4X won't make it go away, alas. */
         tc->flags |= TCF_FUN_HEAVYWEIGHT;
-        tc->noteLocalOverwritesArguments();
+        tc->noteBindingsAccessedDynamically();
         pn = qualifiedSuffix(pn);
     }
     return pn;
 }
 
 ParseNode *
 Parser::attributeIdentifier()
 {
@@ -6573,17 +6597,17 @@ Parser::propertyQualifiedIdentifier()
 {
     JS_ASSERT(!tc->inStrictMode());
     JS_ASSERT(tokenStream.isCurrentTokenType(TOK_NAME));
     JS_ASSERT(tokenStream.currentToken().t_op == JSOP_NAME);
     JS_ASSERT(tokenStream.peekToken() == TOK_DBLCOLON);
 
     /* Deoptimize QualifiedIdentifier properties to avoid tricky analysis. */
     tc->flags |= TCF_FUN_HEAVYWEIGHT;
-    tc->noteLocalOverwritesArguments();
+    tc->noteBindingsAccessedDynamically();
 
     PropertyName *name = tokenStream.currentToken().name();
     ParseNode *node = NameNode::create(PNK_NAME, name, tc);
     if (!node)
         return NULL;
     node->setOp(JSOP_NAME);
     node->pn_dflags |= PND_DEOPTIMIZED;
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/testDynamicLookup.js
@@ -0,0 +1,68 @@
+(function() { var x = 2; eval("assertEq(x, 2)"); })();
+(function() { var x = 2; (function() { assertEq(x, 2) })() })();
+(function() { var x = 2; (function() { eval("assertEq(x, 2)") })() })();
+(function() { var x = 2; (function() { (function() { assertEq(x, 2) })()})() })();
+(function() { var x = 2; (function() { (function() { eval("assertEq(x, 2)") })()})() })();
+
+(function() { var x = 2; with({}) { assertEq(x, 2) } })();
+(function() { var x = 2; with({}) { (function() { assertEq(x, 2) })() } })();
+(function() { var x = 3; with({x:2}) { assertEq(x, 2) } })();
+(function() { var x = 3; with({x:2}) { (function() { assertEq(x, 2) })() } })();
+(function() { var x = 2; (function() { with({}) { assertEq(x, 2) } })() })();
+(function() { var x = 2; (function() { with({}) { (function() { assertEq(x, 2) })() } })() })();
+(function() { var x = 3; (function() { with({x:2}) { assertEq(x, 2) } })() })();
+(function() { var x = 3; (function() { with({x:2}) { (function() { assertEq(x, 2) })() } })() })();
+
+(function() { if (Math) function x() {}; assertEq(typeof x, "function") })();
+(function() { if (Math) function x() {}; eval('assertEq(typeof x, "function")') })();
+(function() { if (Math) function x() {}; (function() { assertEq(typeof x, "function") })() })();
+(function() { if (Math) function x() {}; (function() { eval('assertEq(typeof x, "function")') })() })();
+
+(function() { eval("var x = 2"); assertEq(x, 2) })();
+(function() { eval("var x = 2"); (function() { assertEq(x, 2) })() })();
+(function() { eval("var x = 2"); (function() { (function() { assertEq(x, 2) })() })() })();
+
+(function() { var x = 2; (function() { eval('var y = 3'); assertEq(x, 2) })() })();
+(function() { var x = 2; (function() { eval('var y = 3'); (function() { assertEq(x, 2) })() })() })();
+
+(function() { var x = 3; (function() { eval('var x = 2'); assertEq(x, 2) })() })();
+(function() { var x = 3; (function() { eval('var x = 2'); (function() { assertEq(x, 2) })() })() })();
+
+(function() { var x = 2; eval("eval('assertEq(x, 2)')") })();
+(function() { var x = 2; (function() { eval("eval('assertEq(x, 2)')") })() })();
+(function() { var x = 2; eval("(function() { eval('assertEq(x, 2)') })()") })();
+(function() { var x = 2; (function() { eval("(function() { eval('assertEq(x, 2)') })()") })() })();
+(function() { var x = 2; (function() { eval("(function() { eval('(function() { assertEq(x, 2) })()') })()") })() })();
+
+(function() { var [x] = [2]; eval('assertEq(x, 2)') })();
+(function() { var [x] = [2]; (function() { assertEq(x, 2) })() })();
+(function() { var [x] = [2]; (function() { eval('assertEq(x, 2)') })() })();
+(function() { for (var [x] = [2];;) { return (function() { return assertEq(x, 2); })() } })();
+(function() { for (var [x] = [2];;) { return (function() { return eval('assertEq(x, 2)'); })() } })();
+(function() { let ([x] = [2]) { eval('assertEq(x, 2)') } })();
+(function() { let ([x] = [2]) { (function() { assertEq(x, 2) })() } })();
+(function() { let ([x] = [2]) { (function() { eval('assertEq(x, 2)') })() } })();
+
+(function() { var {y:x} = {y:2}; eval('assertEq(x, 2)') })();
+(function() { var {y:x} = {y:2}; (function() { assertEq(x, 2) })() })();
+(function() { var {y:x} = {y:2}; (function() { eval('assertEq(x, 2)') })() })();
+(function() { for (var {y:x} = {y:2};;) { return (function() { return assertEq(x, 2); })() } })();
+(function() { for (var {y:x} = {y:2};;) { return (function() { return eval('assertEq(x, 2)'); })() } })();
+(function() { let ({y:x} = {y:2}) { eval('assertEq(x, 2)') } })();
+(function() { let ({y:x} = {y:2}) { (function() { assertEq(x, 2) })() } })();
+(function() { let ({y:x} = {y:2}) { (function() { eval('assertEq(x, 2)') })() } })();
+
+(function([x]) { eval('assertEq(x, 2)') })([2]);
+(function([x]) { (function() { assertEq(x, 2) })() })([2]);
+(function([x]) { (function() { eval('assertEq(x, 2)') })() })([2]);
+
+(function f() { assertEq(f.length, 0) })();
+(function f() { eval('assertEq(f.length, 0)') })();
+(function f() { (function f(x) { eval('assertEq(f.length, 1)') })() })();
+(function f() { eval("(function f(x) { eval('assertEq(f.length, 1)') })()") })();
+
+function f1() { assertEq(typeof f1, "function") }; f1();
+with({}) { (function() { assertEq(typeof f1, "function") })() }
+if (Math)
+    function f2(x) {}
+assertEq(f2.length, 1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/testFunctionStatementAliasLocals.js
@@ -0,0 +1,26 @@
+function f1(b) {
+    var w = 3;
+    if (b)
+        function w() {}
+    return w;
+}
+assertEq(typeof f1(true), "function");
+assertEq(f1(false), 3);
+
+function f2(b, w) {
+    if (b)
+        function w() {}
+    return w;
+}
+assertEq(typeof f2(true, 3), "function");
+assertEq(f2(false, 3), 3);
+
+function f3(b) {
+    let (w = 3) {
+        if (b)
+            function w() {}
+        return w;
+    }
+}
+assertEq(f3(true, 3), 3);
+assertEq(f3(false), 3);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/testWeirdThingsInFunctionConstructor.js
@@ -0,0 +1,6 @@
+var f = new Function('x', 'var f = 3; if (x) function f() {}; return f');
+assertEq(f(false), 3);
+assertEq(typeof f(true), "function");
+
+var f = new Function('x', '"use strict"; x = 3; return arguments[0]');
+assertEq(f(42), 42);
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -179,28 +179,30 @@ ScriptAnalysis::analyzeBytecode(JSContex
      * be indirectly read but not written through 'arguments' properties).
      * All escaping locals are treated as having possible use-before-defs.
      * Conservatively use 'mayNeedArgsObj' instead of 'needsArgsObj'
      * (needsArgsObj requires SSA which requires escapedSlots).
      */
 
     PodZero(escapedSlots, numSlots);
 
-    if (script->usesEval || script->mayNeedArgsObj() || script->compartment()->debugMode()) {
+    if (script->bindingsAccessedDynamically || script->mayNeedArgsObj() ||
+        script->compartment()->debugMode())
+    {
         for (unsigned i = 0; i < nargs; i++)
             escapedSlots[ArgSlot(i)] = true;
     } else {
         for (uint32_t i = 0; i < script->numClosedArgs(); i++) {
             unsigned arg = script->getClosedArg(i);
             JS_ASSERT(arg < nargs);
             escapedSlots[ArgSlot(arg)] = true;
         }
     }
 
-    if (script->usesEval || script->compartment()->debugMode()) {
+    if (script->bindingsAccessedDynamically || script->compartment()->debugMode()) {
         for (unsigned i = 0; i < script->nfixed; i++)
             escapedSlots[LocalSlot(script, i)] = true;
     } else {
         for (uint32_t i = 0; i < script->numClosedVars(); i++) {
             unsigned local = script->getClosedVar(i);
             JS_ASSERT(local < script->nfixed);
             escapedSlots[LocalSlot(script, local)] = true;
         }
@@ -214,17 +216,19 @@ ScriptAnalysis::analyzeBytecode(JSContex
         usesReturnValue_ = true;
 
     bool heavyweight = script->function() && script->function()->isHeavyweight();
 
     isCompileable = true;
 
     isInlineable = true;
     if (script->numClosedArgs() || script->numClosedVars() || heavyweight ||
-        script->usesEval || script->mayNeedArgsObj() || cx->compartment->debugMode()) {
+        script->bindingsAccessedDynamically || script->mayNeedArgsObj() ||
+        cx->compartment->debugMode())
+    {
         isInlineable = false;
     }
 
     modifiesArguments_ = false;
     if (script->numClosedArgs() || heavyweight)
         modifiesArguments_ = true;
 
     canTrackVars = true;
@@ -1640,17 +1644,17 @@ ScriptAnalysis::analyzeSSA(JSContext *cx
      * from the stack frame directly.
      */
     if (script->analyzedArgsUsage())
         return;
 
     /* Ensured by analyzeBytecode. */
     JS_ASSERT(script->function());
     JS_ASSERT(script->mayNeedArgsObj());
-    JS_ASSERT(!script->usesEval);
+    JS_ASSERT(!script->bindingsAccessedDynamically);
 
     /*
      * Since let variables are not tracked, we cannot soundly perform this
      * analysis in their presence.
      */
     if (localsAliasStack()) {
         script->setNeedsArgsObj(true);
         return;
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -565,16 +565,19 @@ JS_GetFrameScopeChain(JSContext *cx, JSS
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_GetFrameCallObject(JSContext *cx, JSStackFrame *fpArg)
 {
     StackFrame *fp = Valueify(fpArg);
     JS_ASSERT(cx->stack.containsSlow(fp));
 
+    if (!fp->compartment()->debugMode())
+        return NULL;
+
     if (!fp->isFunctionFrame())
         return NULL;
 
     js::AutoCompartment ac(cx, &fp->scopeChain());
     if (!ac.enter())
         return NULL;
 
     /*
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1194,17 +1194,17 @@ js_NewFunction(JSContext *cx, JSObject *
     }
     fun = static_cast<JSFunction *>(funobj);
 
     /* Initialize all function members. */
     fun->nargs = uint16_t(nargs);
     fun->flags = flags & (JSFUN_FLAGS_MASK | JSFUN_KINDMASK);
     if ((flags & JSFUN_KINDMASK) >= JSFUN_INTERPRETED) {
         JS_ASSERT(!native);
-        fun->script().init(NULL);
+        fun->mutableScript().init(NULL);
         fun->initEnvironment(parent);
     } else {
         fun->u.native = native;
         JS_ASSERT(fun->u.native);
     }
     if (kind == JSFunction::ExtendedFinalizeKind) {
         fun->flags |= JSFUN_EXTENDED;
         fun->initializeExtended();
@@ -1260,17 +1260,17 @@ js_CloneFunctionObject(JSContext *cx, JS
          * functions.
          */
         if (clone->isInterpreted()) {
             JSScript *script = clone->script();
             JS_ASSERT(script);
             JS_ASSERT(script->compartment() == fun->compartment());
             JS_ASSERT(script->compartment() != cx->compartment);
 
-            clone->script().init(NULL);
+            clone->mutableScript().init(NULL);
             JSScript *cscript = CloneScript(cx, script);
             if (!cscript)
                 return NULL;
 
             cscript->globalObject = &clone->global();
             clone->setScript(cscript);
             if (!cscript->typeSetFunction(cx, clone))
                 return NULL;
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -130,26 +130,31 @@ struct JSFunction : public JSObject
      * activations (stack frames) of the function.
      */
     inline JSObject *environment() const;
     inline void setEnvironment(JSObject *obj);
     inline void initEnvironment(JSObject *obj);
 
     static inline size_t offsetOfEnvironment() { return offsetof(JSFunction, u.i.env_); }
 
-    js::HeapPtrScript &script() const {
+    JSScript *script() const {
+        JS_ASSERT(isInterpreted());
+        return *(js::HeapPtrScript *)&u.i.script_;
+    }
+
+    js::HeapPtrScript &mutableScript() {
         JS_ASSERT(isInterpreted());
         return *(js::HeapPtrScript *)&u.i.script_;
     }
 
     inline void setScript(JSScript *script_);
     inline void initScript(JSScript *script_);
 
     JSScript *maybeScript() const {
-        return isInterpreted() ? script().get() : NULL;
+        return isInterpreted() ? script() : NULL;
     }
 
     JSNative native() const {
         JS_ASSERT(isNative());
         return u.native;
     }
 
     JSNative maybeNative() const {
--- a/js/src/jsfuninlines.h
+++ b/js/src/jsfuninlines.h
@@ -282,19 +282,19 @@ CloneFunctionObject(JSContext *cx, JSFun
 }
 
 } /* namespace js */
 
 inline void
 JSFunction::setScript(JSScript *script_)
 {
     JS_ASSERT(isInterpreted());
-    script() = script_;
+    mutableScript() = script_;
 }
 
 inline void
 JSFunction::initScript(JSScript *script_)
 {
     JS_ASSERT(isInterpreted());
-    script().init(script_);
+    mutableScript().init(script_);
 }
 
 #endif /* jsfuninlines_h___ */
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -392,17 +392,17 @@ XDRScriptConst(XDRState<mode> *xdr, Heap
 template<XDRMode mode>
 bool
 js::XDRScript(XDRState<mode> *xdr, JSScript **scriptp, JSScript *parentScript)
 {
     enum ScriptBits {
         NoScriptRval,
         SavedCallerFun,
         StrictModeCode,
-        UsesEval,
+        ContainsDynamicNameAccess,
         MayNeedArgsObj,
         NeedsArgsObj,
         OwnFilename,
         ParentFilename
     };
 
     uint32_t length, lineno, nslots;
     uint32_t natoms, nsrcnotes, ntrynotes, nobjects, nregexps, nconsts, nClosedArgs, nClosedVars, i;
@@ -547,18 +547,18 @@ js::XDRScript(XDRState<mode> *xdr, JSScr
         nTypeSets = script->nTypeSets;
 
         if (script->noScriptRval)
             scriptBits |= (1 << NoScriptRval);
         if (script->savedCallerFun)
             scriptBits |= (1 << SavedCallerFun);
         if (script->strictModeCode)
             scriptBits |= (1 << StrictModeCode);
-        if (script->usesEval)
-            scriptBits |= (1 << UsesEval);
+        if (script->bindingsAccessedDynamically)
+            scriptBits |= (1 << ContainsDynamicNameAccess);
         if (script->mayNeedArgsObj()) {
             scriptBits |= (1 << MayNeedArgsObj);
             /*
              * In some cases, the front-end calls setNeedsArgsObj when the
              * script definitely needsArgsObj; preserve this information which
              * would otherwise be lost.
              */
             if (script->analyzedArgsUsage() && script->needsArgsObj())
@@ -624,18 +624,18 @@ js::XDRScript(XDRState<mode> *xdr, JSScr
         *scriptp = script;
 
         if (scriptBits & (1 << NoScriptRval))
             script->noScriptRval = true;
         if (scriptBits & (1 << SavedCallerFun))
             script->savedCallerFun = true;
         if (scriptBits & (1 << StrictModeCode))
             script->strictModeCode = true;
-        if (scriptBits & (1 << UsesEval))
-            script->usesEval = true;
+        if (scriptBits & (1 << ContainsDynamicNameAccess))
+            script->bindingsAccessedDynamically = true;
         if (scriptBits & (1 << MayNeedArgsObj)) {
             script->setMayNeedArgsObj();
             if (scriptBits & (1 << NeedsArgsObj))
                 script->setNeedsArgsObj(true);
         } else {
             JS_ASSERT(!(scriptBits & (1 << NeedsArgsObj)));
         }
     }
@@ -1258,18 +1258,18 @@ JSScript::NewScriptFromEmitter(JSContext
     if (bce->flags & TCF_STRICT_MODE_CODE)
         script->strictModeCode = true;
     if (bce->flags & TCF_COMPILE_N_GO) {
         script->compileAndGo = true;
         const StackFrame *fp = bce->parser->callerFrame;
         if (fp && fp->isFunctionFrame())
             script->savedCallerFun = true;
     }
-    if (bce->callsEval())
-        script->usesEval = true;
+    if (bce->bindingsAccessedDynamically())
+        script->bindingsAccessedDynamically = true;
     if (bce->flags & TCF_HAS_SINGLETONS)
         script->hasSingletons = true;
 
     /*
      * The arguments-usage analysis in analyzeSSA only looks at
      * JSOP_ARGUMENTS use. Therefore, anything else that definitely requires an
      * arguments object needs to be accounted for here.
      */
@@ -1934,8 +1934,45 @@ JSScript::applySpeculationFailed(JSConte
                 JS_ASSERT(set->isLazyArguments(cx));
                 set->addType(cx, types::Type::UnknownType());
             }
         }
     }
 
     return true;
 }
+
+#ifdef DEBUG
+bool
+JSScript::varIsAliased(unsigned varSlot)
+{
+    if (bindingsAccessedDynamically)
+        return true;
+
+    for (uint32_t i = 0; i < numClosedVars(); ++i) {
+        if (closedVars()->vector[i] == varSlot) {
+            JS_ASSERT(function()->isHeavyweight());
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool
+JSScript::argIsAliased(unsigned argSlot)
+{
+    if (bindingsAccessedDynamically)
+        return true;
+
+    if (needsArgsObj())
+        return true;
+
+    for (uint32_t i = 0; i < numClosedArgs(); ++i) {
+        if (closedArgs()->vector[i] == argSlot) {
+            JS_ASSERT(function()->isHeavyweight());
+            return true;
+        }
+    }
+
+    return false;
+}
+#endif
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -509,17 +509,17 @@ struct JSScript : public js::gc::Cell
     // 1-bit fields.
 
   public:
     bool            noScriptRval:1; /* no need for result value of last
                                        expression statement */
     bool            savedCallerFun:1; /* can call getCallerFunction() */
     bool            strictModeCode:1; /* code is in strict mode */
     bool            compileAndGo:1;   /* script was compiled with TCF_COMPILE_N_GO */
-    bool            usesEval:1;       /* script uses eval() */
+    bool            bindingsAccessedDynamically:1; /* see TCF_BINDINGS_ACCESSED_DYNAMICALLY */
     bool            warnedAboutTwoArgumentEval:1; /* have warned about use of
                                                      obsolete eval(s, o) in
                                                      this script */
     bool            warnedAboutUndefinedProp:1; /* have warned about uses of
                                                    undefined properties in this
                                                    script */
     bool            hasSingletons:1;  /* script has singleton objects */
     bool            isOuterFunction:1; /* function is heavyweight, with inner functions */
@@ -801,16 +801,21 @@ struct JSScript : public js::gc::Cell
     }
 
     uint32_t getClosedVar(uint32_t index) {
         js::ClosedSlotArray *arr = closedVars();
         JS_ASSERT(index < arr->length);
         return arr->vector[index];
     }
 
+
+#ifdef DEBUG
+    bool varIsAliased(unsigned varSlot);
+    bool argIsAliased(unsigned argSlot);
+#endif
   private:
     /*
      * Recompile with or without single-stepping support, as directed
      * by stepModeEnabled().
      */
     void recompileForStepMode(js::FreeOp *fop);
 
     /* Attempt to change this->stepMode to |newValue|. */
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -206,20 +206,22 @@ ArgGetter(JSContext *cx, JSObject *obj, 
     NormalArgumentsObject &argsobj = obj->asNormalArguments();
     if (JSID_IS_INT(id)) {
         /*
          * arg can exceed the number of arguments if a script changed the
          * prototype to point to another Arguments object with a bigger argc.
          */
         unsigned arg = unsigned(JSID_TO_INT(id));
         if (arg < argsobj.initialLength() && !argsobj.isElementDeleted(arg)) {
-            if (StackFrame *fp = argsobj.maybeStackFrame())
+            if (StackFrame *fp = argsobj.maybeStackFrame()) {
+                JS_ASSERT_IF(arg < fp->numFormalArgs(), fp->script()->argIsAliased(arg));
                 *vp = fp->canonicalActualArg(arg);
-            else
+            } else {
                 *vp = argsobj.element(arg);
+            }
         }
     } else if (JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom)) {
         if (!argsobj.hasOverriddenLength())
             vp->setInt32(argsobj.initialLength());
     } else {
         JS_ASSERT(JSID_IS_ATOM(id, cx->runtime->atomState.calleeAtom));
         const Value &v = argsobj.callee();
         if (!v.isMagic(JS_OVERWRITTEN_CALLEE))
@@ -237,18 +239,20 @@ ArgSetter(JSContext *cx, JSObject *obj, 
     NormalArgumentsObject &argsobj = obj->asNormalArguments();
 
     if (JSID_IS_INT(id)) {
         unsigned arg = unsigned(JSID_TO_INT(id));
         if (arg < argsobj.initialLength()) {
             if (StackFrame *fp = argsobj.maybeStackFrame()) {
                 JSScript *script = fp->functionScript();
                 JS_ASSERT(script->needsArgsObj());
-                if (arg < fp->numFormalArgs())
+                if (arg < fp->numFormalArgs()) {
+                    JS_ASSERT(fp->script()->argIsAliased(arg));
                     types::TypeScript::SetArgument(cx, script, arg, *vp);
+                }
                 fp->canonicalActualArg(arg) = *vp;
                 return true;
             }
         }
     } else {
         JS_ASSERT(JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom) ||
                   JSID_IS_ATOM(id, cx->runtime->atomState.calleeAtom));
     }
--- a/js/src/vm/ScopeObject-inl.h
+++ b/js/src/vm/ScopeObject-inl.h
@@ -216,16 +216,17 @@ inline uint32_t
 BlockObject::slotCount() const
 {
     return propertyCount();
 }
 
 inline HeapSlot &
 BlockObject::slotValue(unsigned i)
 {
+    JS_ASSERT(i < slotCount());
     return getSlotRef(RESERVED_SLOTS + i);
 }
 
 inline StaticBlockObject *
 StaticBlockObject::enclosingBlock() const
 {
     JSObject *obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
     return obj ? &obj->asStaticBlock() : NULL;
@@ -254,19 +255,25 @@ StaticBlockObject::setDefinitionParseNod
 inline Definition *
 StaticBlockObject::maybeDefinitionParseNode(unsigned i)
 {
     Value v = slotValue(i);
     return v.isUndefined() ? NULL : reinterpret_cast<Definition *>(v.toPrivate());
 }
 
 inline void
-StaticBlockObject::poisonDefinitionParseNode(unsigned i)
+StaticBlockObject::setAliased(unsigned i, bool aliased)
 {
-    slotValue(i).init(this, i, PrivateValue(NULL));
+    slotValue(i).init(this, i, BooleanValue(aliased));
+}
+
+inline bool
+StaticBlockObject::isAliased(unsigned i)
+{
+    return slotValue(i).isTrue();
 }
 
 inline StaticBlockObject &
 ClonedBlockObject::staticBlock() const
 {
     return getProto()->asStaticBlock();
 }
 
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -80,17 +80,17 @@ js_PutCallObject(StackFrame *fp)
         unsigned n = bindings.countLocalNames();
         if (n > 0) {
             uint32_t nvars = bindings.countVars();
             uint32_t nargs = bindings.countArgs();
             JS_ASSERT(fun->nargs == nargs);
             JS_ASSERT(nvars + nargs == n);
 
             JSScript *script = fun->script();
-            if (script->usesEval
+            if (script->bindingsAccessedDynamically
 #ifdef JS_METHODJIT
                 || script->debugMode
 #endif
                 ) {
                 callobj.copyValues(nargs, fp->formalArgs(), nvars, fp->slots());
             } else {
                 /*
                  * For each arg & var that is closed over, copy it from the stack
@@ -259,31 +259,33 @@ CallObject::createForStrictEval(JSContex
     callobj->setStackFrame(fp);
     fp->setScopeChainWithOwnCallObj(*callobj);
     return callobj;
 }
 
 JSBool
 CallObject::getArgumentsOp(JSContext *cx, JSObject *obj, jsid id, Value *vp)
 {
-    *vp = obj->asCall().arguments();
+    CallObject &callobj = obj->asCall();
+    *vp = callobj.arguments();
 
     /*
      * This can only happen through eval-in-frame. Eventually, this logic can
      * be hoisted into debugger scope wrappers. That will allow 'arguments' to
      * be a pure data property and allow call_resolve to be removed.
      */
     if (vp->isMagic(JS_UNASSIGNED_ARGUMENTS)) {
-        StackFrame *fp = obj->asCall().maybeStackFrame();
+        JS_ASSERT(callobj.compartment()->debugMode());
+        StackFrame *fp = callobj.maybeStackFrame();
         ArgumentsObject *argsObj = ArgumentsObject::createUnexpected(cx, fp);
         if (!argsObj)
             return false;
 
         *vp = ObjectValue(*argsObj);
-        obj->asCall().setArguments(*vp);
+        callobj.setArguments(*vp);
     }
 
     return true;
 }
 
 JSBool
 CallObject::setArgumentsOp(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp)
 {
@@ -291,82 +293,92 @@ CallObject::setArgumentsOp(JSContext *cx
     obj->asCall().setArguments(*vp);
     return true;
 }
 
 JSBool
 CallObject::getArgOp(JSContext *cx, JSObject *obj, jsid id, Value *vp)
 {
     CallObject &callobj = obj->asCall();
+
     JS_ASSERT((int16_t) JSID_TO_INT(id) == JSID_TO_INT(id));
     unsigned i = (uint16_t) JSID_TO_INT(id);
 
+    DebugOnly<JSScript *> script = callobj.getCalleeFunction()->script();
+    JS_ASSERT_IF(!callobj.compartment()->debugMode(), script->argIsAliased(i));
+
     if (StackFrame *fp = callobj.maybeStackFrame())
         *vp = fp->formalArg(i);
     else
         *vp = callobj.arg(i);
     return true;
 }
 
 JSBool
 CallObject::setArgOp(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp)
 {
     CallObject &callobj = obj->asCall();
+
     JS_ASSERT((int16_t) JSID_TO_INT(id) == JSID_TO_INT(id));
     unsigned i = (uint16_t) JSID_TO_INT(id);
 
+    JSScript *script = callobj.getCalleeFunction()->script();
+    JS_ASSERT_IF(!callobj.compartment()->debugMode(), script->argIsAliased(i));
+
     if (StackFrame *fp = callobj.maybeStackFrame())
         fp->formalArg(i) = *vp;
     else
         callobj.setArg(i, *vp);
 
-    JSFunction *fun = callobj.getCalleeFunction();
-    JSScript *script = fun->script();
     if (!script->ensureHasTypes(cx))
         return false;
 
     TypeScript::SetArgument(cx, script, i, *vp);
 
     return true;
 }
 
 JSBool
 CallObject::getVarOp(JSContext *cx, JSObject *obj, jsid id, Value *vp)
 {
     CallObject &callobj = obj->asCall();
+
     JS_ASSERT((int16_t) JSID_TO_INT(id) == JSID_TO_INT(id));
     unsigned i = (uint16_t) JSID_TO_INT(id);
 
+    DebugOnly<JSScript *> script = callobj.getCalleeFunction()->script();
+    JS_ASSERT_IF(!callobj.compartment()->debugMode(), script->varIsAliased(i));
+
     if (StackFrame *fp = callobj.maybeStackFrame())
         *vp = fp->varSlot(i);
     else
         *vp = callobj.var(i);
     return true;
 }
 
 JSBool
 CallObject::setVarOp(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp)
 {
     CallObject &callobj = obj->asCall();
 
     JS_ASSERT((int16_t) JSID_TO_INT(id) == JSID_TO_INT(id));
     unsigned i = (uint16_t) JSID_TO_INT(id);
 
+    JSScript *script = callobj.getCalleeFunction()->script();
+    JS_ASSERT_IF(!callobj.compartment()->debugMode(), script->varIsAliased(i));
+
     if (StackFrame *fp = callobj.maybeStackFrame())
         fp->varSlot(i) = *vp;
     else
         callobj.setVar(i, *vp);
 
-    JSFunction *fun = callobj.getCalleeFunction();
-    JSScript *script = fun->script();
     if (!script->ensureHasTypes(cx))
         return false;
 
     TypeScript::SetLocal(cx, script, i, *vp);
-
     return true;
 }
 
 bool
 CallObject::containsVarOrArg(PropertyName *name, Value *vp, JSContext *cx)
 {
     jsid id = ATOM_TO_JSID(name);
     const Shape *shape = nativeLookup(cx, id);
@@ -820,17 +832,18 @@ block_getProperty(JSContext *cx, JSObjec
 {
     /*
      * Block objects are never exposed to script, and the engine handles them
      * with care. So unlike other getters, this one can assert (rather than
      * check) certain invariants about obj.
      */
     ClonedBlockObject &block = obj->asClonedBlock();
     unsigned index = (unsigned) JSID_TO_INT(id);
-    JS_ASSERT(index < block.slotCount());
+
+    JS_ASSERT_IF(!block.compartment()->debugMode(), block.staticBlock().isAliased(index));
 
     if (StackFrame *fp = block.maybeStackFrame()) {
         fp = js_LiveFrameIfGenerator(fp);
         index += fp->numFixed() + block.stackDepth();
         JS_ASSERT(index < fp->numSlots());
         *vp = fp->slots()[index];
         return true;
     }
@@ -840,17 +853,18 @@ block_getProperty(JSContext *cx, JSObjec
     return true;
 }
 
 static JSBool
 block_setProperty(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp)
 {
     ClonedBlockObject &block = obj->asClonedBlock();
     unsigned index = (unsigned) JSID_TO_INT(id);
-    JS_ASSERT(index < block.slotCount());
+
+    JS_ASSERT_IF(!block.compartment()->debugMode(), block.staticBlock().isAliased(index));
 
     if (StackFrame *fp = block.maybeStackFrame()) {
         fp = js_LiveFrameIfGenerator(fp);
         index += fp->numFixed() + block.stackDepth();
         JS_ASSERT(index < fp->numSlots());
         fp->slots()[index] = *vp;
         return true;
     }
@@ -1019,16 +1033,23 @@ js::XDRStaticBlockObject(XDRState<mode> 
                       ? ATOM_TO_JSID(atom)
                       : INT_TO_JSID(i);
 
             bool redeclared;
             if (!obj->addVar(cx, id, i, &redeclared)) {
                 JS_ASSERT(!redeclared);
                 return false;
             }
+
+            uint32_t aliased;
+            if (!xdr->codeUint32(&aliased))
+                return false;
+
+            JS_ASSERT(aliased == 0 || aliased == 1);
+            obj->setAliased(i, !!aliased);
         }
     } else {
         AutoShapeVector shapes(cx);
         shapes.growBy(count);
 
         for (Shape::Range r(obj->lastProperty()); !r.empty(); r.popFront()) {
             const Shape *shape = &r.front();
             shapes[shape->shortid()] = shape;
@@ -1048,16 +1069,20 @@ js::XDRStaticBlockObject(XDRState<mode> 
 
             /* The empty string indicates an int id. */
             JSAtom *atom = JSID_IS_ATOM(propid)
                            ? JSID_TO_ATOM(propid)
                            : cx->runtime->emptyString;
 
             if (!XDRAtom(xdr, &atom))
                 return false;
+
+            uint32_t aliased = obj->isAliased(i);
+            if (!xdr->codeUint32(&aliased))
+                return false;
         }
     }
     return true;
 }
 
 template bool
 js::XDRStaticBlockObject(XDRState<XDR_ENCODE> *xdr, JSScript *script, StaticBlockObject **objp);
 
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -245,17 +245,23 @@ class StaticBlockObject : public BlockOb
     void setStackDepth(uint32_t depth);
 
     /*
      * 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);
-    void poisonDefinitionParseNode(unsigned i);
+
+    /*
+     * A let binding is aliased is accessed lexically by nested functions or
+     * dynamically through dynamic name lookup (eval, with, function::, etc).
+     */
+    void setAliased(unsigned i, bool aliased);
+    bool isAliased(unsigned i);
 
     const Shape *addVar(JSContext *cx, jsid id, int index, bool *redeclared);
 };
 
 class ClonedBlockObject : public BlockObject
 {
   public:
     static ClonedBlockObject *create(JSContext *cx, StaticBlockObject &block, StackFrame *fp);