Bug 659577 - Remove ScopeObject::maybeStackFrame use in the debugger, part 2 (r=jimb)
authorLuke Wagner <luke@mozilla.com>
Wed, 09 May 2012 23:03:12 -0700
changeset 100587 2ff46668b15607f08d7ed8db975729eacbb0d8a2
parent 100586 d657e8cb0201ffe090bb78234fab19147dd4373b
child 100588 4832054e4e422170df2c5864a4e4a82b2e314a6b
push idunknown
push userunknown
push dateunknown
reviewersjimb
bugs659577
milestone15.0a1
Bug 659577 - Remove ScopeObject::maybeStackFrame use in the debugger, part 2 (r=jimb)
js/src/jit-test/tests/debug/Frame-eval-14.js
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsscope.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/vm/ArgumentsObject-inl.h
js/src/vm/ScopeObject-inl.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-eval-14.js
@@ -0,0 +1,25 @@
+// Test the corner case of accessing an unaliased variable of a block
+// while the block is not live.
+
+var g = newGlobal('new-compartment');
+g.eval("function h() { debugger }");
+g.eval("function f() { let (x = 1, y) { (function() { y = 0 })(); h() } }");
+g.eval("var surprise = null");
+
+var dbg = new Debugger(g);
+dbg.onDebuggerStatement = function(hFrame) {
+    var fFrame = hFrame.older;
+    assertEq(fFrame.environment.getVariable('x'), 1);
+    assertEq(fFrame.environment.getVariable('y'), 0);
+    fFrame.eval("surprise = function() { return ++x }");
+    assertEq(g.surprise(), 2);
+}
+g.f();
+assertEq(g.surprise !== null, true);
+
+// Either succeed or throw an error about 'x' not being live
+try {
+    assertEq(g.surprise(), 3);
+} catch (e) {
+    assertEq(e+'', 'Error: x is not live');
+}
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -991,17 +991,16 @@ JSContext::JSContext(JSRuntime *rt)
 #endif
     inferenceEnabled(false),
 #ifdef MOZ_TRACE_JSCALLS
     functionCallback(NULL),
 #endif
     enumerators(NULL),
 #ifdef DEBUG
     stackIterAssertionEnabled(true),
-    okToAccessUnaliasedBindings(0),
 #endif
     activeCompilations(0)
 {
     PodZero(&link);
 #ifdef JSGC_ROOT_ANALYSIS
     PodArrayZero(thingGCRooters);
 #ifdef DEBUG
     skipGCRooters = NULL;
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1316,22 +1316,16 @@ struct JSContext : js::ContextFriendFiel
     }
 
 #ifdef DEBUG
     /*
      * Controls whether a quadratic-complexity assertion is performed during
      * stack iteration; defaults to true.
      */
     bool stackIterAssertionEnabled;
-
-    /*
-     * When greather than zero, it is ok to accessed non-aliased fields of
-     * ScopeObjects because the accesses are coming from the DebugScopeProxy.
-     */
-    unsigned okToAccessUnaliasedBindings;
 #endif
 
     /*
      * Count of currently active compilations.
      * When there are compilations active for the context, the GC must not
      * purge the ParseMapPool.
      */
     unsigned activeCompilations;
@@ -1358,33 +1352,16 @@ struct JSContext : js::ContextFriendFiel
      * threshold when p is not null. The function takes the pointer and not
      * a boolean flag to minimize the amount of code in its inlined callers.
      */
     JS_FRIEND_API(void) checkMallocGCPressure(void *p);
 }; /* struct JSContext */
 
 namespace js {
 
-class AutoAllowUnaliasedVarAccess
-{
-    JSContext *cx;
-  public:
-    AutoAllowUnaliasedVarAccess(JSContext *cx) : cx(cx) {
-#ifdef DEBUG
-        cx->okToAccessUnaliasedBindings++;
-#endif
-    }
-    ~AutoAllowUnaliasedVarAccess() {
-#ifdef DEBUG
-        JS_ASSERT(cx->okToAccessUnaliasedBindings);
-        cx->okToAccessUnaliasedBindings--;
-#endif
-    }
-};
-
 struct AutoResolving {
   public:
     enum Kind {
         LOOKUP,
         WATCH
     };
 
     AutoResolving(JSContext *cx, JSObject *obj, jsid id, Kind kind = LOOKUP
--- a/js/src/jsscope.h
+++ b/js/src/jsscope.h
@@ -877,16 +877,21 @@ struct Shape : public js::gc::Cell
      * from the compartment.
      */
     static inline void readBarrier(const Shape *shape);
 
     static inline ThingRootKind rootKind() { return THING_ROOT_SHAPE; }
 
     inline void markChildren(JSTracer *trc);
 
+    inline Shape *search(JSContext *cx, jsid id) {
+        Shape **_;
+        return search(cx, this, id, &_);
+    }
+
     /* For JIT usage */
     static inline size_t offsetOfBase() { return offsetof(Shape, base_); }
 
   private:
     static void staticAsserts() {
         JS_STATIC_ASSERT(offsetof(Shape, base_) == offsetof(js::shadow::Shape, base));
         JS_STATIC_ASSERT(offsetof(Shape, slotInfo) == offsetof(js::shadow::Shape, slotInfo));
         JS_STATIC_ASSERT(FIXED_SLOTS_SHIFT == js::shadow::Shape::FIXED_SLOTS_SHIFT);
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -2208,17 +2208,16 @@ JSScript::applySpeculationFailed(JSConte
     if (script->hasAnalysis() && script->analysis()->ranInference()) {
         types::AutoEnterTypeInference enter(cx);
         types::TypeScript::MonitorUnknown(cx, script, script->argumentsBytecode());
     }
 
     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) {
@@ -2252,9 +2251,8 @@ JSScript::formalLivesInCallObject(unsign
         if (closedArgs()->vector[i] == argSlot) {
             JS_ASSERT(function()->isHeavyweight());
             return true;
         }
     }
 
     return false;
 }
-#endif
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -856,22 +856,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 formalIsAliased(unsigned argSlot);
     bool formalLivesInArgumentsObject(unsigned argSlot);
     bool formalLivesInCallObject(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-inl.h
+++ b/js/src/vm/ArgumentsObject-inl.h
@@ -69,17 +69,17 @@ ArgumentsObject::isAnyElementDeleted() c
 }
 
 inline void
 ArgumentsObject::markElementDeleted(uint32_t i)
 {
     SetBitArrayElement(data()->deletedBits, initialLength(), i);
 }
 
-inline const js::Value &
+inline const Value &
 ArgumentsObject::element(uint32_t i) const
 {
     JS_ASSERT(!isElementDeleted(i));
     return data()->slots[i];
 }
 
 inline void
 ArgumentsObject::setElement(uint32_t i, const js::Value &v)
--- a/js/src/vm/ScopeObject-inl.h
+++ b/js/src/vm/ScopeObject-inl.h
@@ -181,23 +181,30 @@ WithObject::object() const
 }
 
 inline uint32_t
 BlockObject::slotCount() const
 {
     return propertyCount();
 }
 
-inline HeapSlot &
+inline const Value &
 BlockObject::slotValue(unsigned i)
 {
     JS_ASSERT(i < slotCount());
     return getSlotRef(RESERVED_SLOTS + i);
 }
 
+inline void
+BlockObject::setSlotValue(unsigned i, const Value &v)
+{
+    JS_ASSERT(i < slotCount());
+    setSlot(RESERVED_SLOTS + i, v);
+}
+
 inline StaticBlockObject *
 StaticBlockObject::enclosingBlock() const
 {
     JSObject *obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
     return obj ? &obj->asStaticBlock() : NULL;
 }
 
 inline void
@@ -212,30 +219,30 @@ StaticBlockObject::setStackDepth(uint32_
     JS_ASSERT(getReservedSlot(DEPTH_SLOT).isUndefined());
     initReservedSlot(DEPTH_SLOT, PrivateUint32Value(depth));
 }
 
 inline void
 StaticBlockObject::setDefinitionParseNode(unsigned i, Definition *def)
 {
     JS_ASSERT(slotValue(i).isUndefined());
-    slotValue(i).init(this, i, PrivateValue(def));
+    setSlotValue(i, PrivateValue(def));
 }
 
 inline Definition *
 StaticBlockObject::maybeDefinitionParseNode(unsigned i)
 {
     Value v = slotValue(i);
     return v.isUndefined() ? NULL : reinterpret_cast<Definition *>(v.toPrivate());
 }
 
 inline void
 StaticBlockObject::setAliased(unsigned i, bool aliased)
 {
-    slotValue(i).init(this, i, BooleanValue(aliased));
+    setSlotValue(i, BooleanValue(aliased));
     if (aliased)
         JSObject::setPrivate(reinterpret_cast<void *>(1));
 }
 
 inline bool
 StaticBlockObject::isAliased(unsigned i)
 {
     return slotValue(i).isTrue();
@@ -255,22 +262,28 @@ StaticBlockObject::containsVarAtDepth(ui
 
 inline StaticBlockObject &
 ClonedBlockObject::staticBlock() const
 {
     return getProto()->asStaticBlock();
 }
 
 inline const Value &
-ClonedBlockObject::closedSlot(unsigned i)
+ClonedBlockObject::var(unsigned i)
 {
     JS_ASSERT(!maybeStackFrame());
     return slotValue(i);
 }
 
+inline void
+ClonedBlockObject::setVar(unsigned i, const Value &v)
+{
+    setSlotValue(i, v);
+}
+
 }  /* namespace js */
 
 inline js::ScopeObject &
 JSObject::asScope()
 {
     JS_ASSERT(isScope());
     return *static_cast<js::ScopeObject *>(this);
 }
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -199,16 +199,39 @@ CallObject::createForFunction(JSContext 
     CallObject *callobj = create(cx, fp->script(), scopeChain, RootedObject(cx, &fp->callee()));
     if (!callobj)
         return NULL;
 
     callobj->setStackFrame(fp);
     return callobj;
 }
 
+void
+CallObject::copyUnaliasedValues(StackFrame *fp)
+{
+    JS_ASSERT(fp->script() == getCalleeFunction()->script());
+    JSScript *script = fp->script();
+
+    /* If bindings are accessed dynamically, everything is aliased. */
+    if (script->bindingsAccessedDynamically)
+        return;
+
+    /* Copy the unaliased formals. */
+    for (unsigned i = 0; i < script->bindings.numArgs(); ++i) {
+        if (!script->formalLivesInCallObject(i))
+            setArg(i, fp->formalArg(i));
+    }
+
+    /* Copy the unaliased var/let bindings. */
+    for (unsigned i = 0; i < script->bindings.numVars(); ++i) {
+        if (!script->varIsAliased(i))
+            setVar(i, fp->localSlot(i));
+    }
+}
+
 CallObject *
 CallObject::createForStrictEval(JSContext *cx, StackFrame *fp)
 {
     CallObject *callobj = create(cx, fp->script(), fp->scopeChain(), RootedObject(cx));
     if (!callobj)
         return NULL;
 
     callobj->setStackFrame(fp);
@@ -220,17 +243,17 @@ JSBool
 CallObject::getArgOp(JSContext *cx, HandleObject obj, HandleId 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(!cx->okToAccessUnaliasedBindings, script->formalLivesInCallObject(i));
+    JS_ASSERT(script->formalLivesInCallObject(i));
 
     if (StackFrame *fp = callobj.maybeStackFrame())
         *vp = fp->formalArg(i);
     else
         *vp = callobj.arg(i);
     return true;
 }
 
@@ -238,17 +261,17 @@ JSBool
 CallObject::setArgOp(JSContext *cx, HandleObject obj, HandleId 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(!cx->okToAccessUnaliasedBindings, script->formalLivesInCallObject(i));
+    JS_ASSERT(script->formalLivesInCallObject(i));
 
     if (StackFrame *fp = callobj.maybeStackFrame())
         fp->formalArg(i) = *vp;
     else
         callobj.setArg(i, *vp);
 
     if (!script->ensureHasTypes(cx))
         return false;
@@ -262,17 +285,17 @@ JSBool
 CallObject::getVarOp(JSContext *cx, HandleObject obj, HandleId 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(!cx->okToAccessUnaliasedBindings, script->varIsAliased(i));
+    JS_ASSERT(script->varIsAliased(i));
 
     if (StackFrame *fp = callobj.maybeStackFrame())
         *vp = fp->varSlot(i);
     else
         *vp = callobj.var(i);
 
     JS_ASSERT(!vp->isMagic(JS_OPTIMIZED_ARGUMENTS));
     return true;
@@ -282,17 +305,17 @@ JSBool
 CallObject::setVarOp(JSContext *cx, HandleObject obj, HandleId 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(!cx->okToAccessUnaliasedBindings, script->varIsAliased(i));
+    JS_ASSERT(script->varIsAliased(i));
 
     if (StackFrame *fp = callobj.maybeStackFrame())
         fp->varSlot(i) = *vp;
     else
         callobj.setVar(i, *vp);
 
     if (!script->ensureHasTypes(cx))
         return false;
@@ -703,16 +726,27 @@ ClonedBlockObject::put(StackFrame *fp)
     JS_ASSERT(count >= 1);
 
     copySlotRange(RESERVED_SLOTS, fp->base() + depth, count);
 
     /* We must clear the private slot even with errors. */
     setPrivate(NULL);
 }
 
+void
+ClonedBlockObject::copyUnaliasedValues(StackFrame *fp)
+{
+    StaticBlockObject &block = staticBlock();
+    unsigned base = fp->script()->nfixed + stackDepth();
+    for (unsigned i = 0; i < slotCount(); ++i) {
+        if (!block.isAliased(i))
+            setVar(i, fp->localSlot(base + i));
+    }
+}
+
 static JSBool
 block_getProperty(JSContext *cx, HandleObject obj, HandleId id, Value *vp)
 {
     /*
      * 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.
      */
@@ -725,17 +759,17 @@ block_getProperty(JSContext *cx, HandleO
         fp = js_LiveFrameIfGenerator(fp);
         index += fp->numFixed() + block.stackDepth();
         JS_ASSERT(index < fp->numSlots());
         *vp = fp->slots()[index];
         return true;
     }
 
     /* Values are in slots immediately following the class-reserved ones. */
-    JS_ASSERT(block.closedSlot(index) == *vp);
+    JS_ASSERT(block.var(index) == *vp);
     return true;
 }
 
 static JSBool
 block_setProperty(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, Value *vp)
 {
     ClonedBlockObject &block = obj->asClonedBlock();
     unsigned index = (unsigned) JSID_TO_INT(id);
@@ -1230,26 +1264,131 @@ namespace js {
 
 /*
  * DebugScopeProxy is the handler for DebugScopeObject proxy objects and mostly
  * just wraps ScopeObjects. Having a custom handler (rather than trying to
  * reuse js::Wrapper) gives us several important abilities:
  *  - We want to pass the ScopeObject as the receiver to forwarded scope
  *    property ops so that Call/Block/With ops do not all require a
  *    'normalization' step.
+ *  - The debug scope proxy can directly manipulate the stack frame to allow
+ *    the debugger to read/write args/locals that were otherwise unaliased.
  *  - The engine has made certain assumptions about the possible reads/writes
  *    in a scope. DebugScopeProxy allows us to prevent the debugger from
  *    breaking those assumptions. Examples include adding shadowing variables
  *    or changing the property attributes of bindings.
  *  - The engine makes optimizations that are observable to the debugger. The
  *    proxy can either hide these optimizations or make the situation more
  *    clear to the debugger. An example is 'arguments'.
  */
 class DebugScopeProxy : public BaseProxyHandler
 {
+    enum Action { SET, GET };
+
+    /*
+     * This function handles access to unaliased locals/formals. If such
+     * accesses were passed on directly to the DebugScopeObject::scope, they
+     * would not be reading/writing the canonical location for the variable,
+     * which is on the stack. Thus, handleUn must translate would-be
+     * accesses to scope objects into analogous accesses of the stack frame.
+     *
+     * handleUnaliasedAccess returns 'true' if the access was unaliased and
+     * completed by handleUnaliasedAccess.
+     */
+    bool handleUnaliasedAccess(JSContext *cx, ScopeObject &scope, jsid id, Action action, Value *vp)
+    {
+        Shape *shape = scope.lastProperty()->search(cx, id);
+        if (!shape)
+            return false;
+
+        StackFrame *maybefp = cx->runtime->debugScopes->hasLiveFrame(scope);
+
+        if (scope.isCall() && !scope.asCall().isForEval()) {
+            CallObject &callobj = scope.asCall();
+            JSScript *script = callobj.getCalleeFunction()->script();
+            if (!script->ensureHasTypes(cx))
+                return false;
+
+            if (shape->setterOp() == CallObject::setVarOp) {
+                unsigned i = shape->shortid();
+                if (script->varIsAliased(i))
+                    return false;
+                
+                if (maybefp) {
+                    if (action == GET)
+                        *vp = maybefp->varSlot(i);
+                    else
+                        maybefp->varSlot(i) = *vp;
+                } else {
+                    if (action == GET)
+                        *vp = callobj.var(i);
+                    else
+                        callobj.setVar(i, *vp);
+                }
+
+                if (action == SET)
+                    TypeScript::SetLocal(cx, script, i, *vp);
+
+                return true;
+            }
+
+            if (shape->setterOp() == CallObject::setArgOp) {
+                unsigned i = shape->shortid();
+                if (script->formalLivesInCallObject(i))
+                    return false;
+                
+                if (maybefp) {
+                    if (action == GET)
+                        *vp = maybefp->formalArg(i);
+                    else
+                        maybefp->formalArg(i) = *vp;
+                } else {
+                    if (action == GET)
+                        *vp = callobj.arg(i);
+                    else
+                        callobj.setArg(i, *vp);
+                }
+
+                if (action == SET)
+                    TypeScript::SetArgument(cx, script, i, *vp);
+
+                return true;
+            }
+
+            return false;
+        }
+
+        if (scope.isClonedBlock()) {
+            ClonedBlockObject &block = scope.asClonedBlock();
+            unsigned i = shape->shortid();
+            if (block.staticBlock().isAliased(i))
+                return false;
+
+            if (maybefp) {
+                JSScript *script = maybefp->script();
+                unsigned local = i + script->nfixed + block.stackDepth();
+                if (action == GET)
+                    *vp = maybefp->localSlot(local);
+                else
+                    maybefp->localSlot(local) = *vp;
+                JS_ASSERT(analyze::LocalSlot(script, local) >= analyze::TotalSlots(script));
+            } else {
+                if (action == GET)
+                    *vp = block.var(i);
+                else
+                    block.setVar(i, *vp);
+            }
+
+            return true;
+        }
+
+        JS_ASSERT(scope.isDeclEnv() || scope.isWith() || scope.asCall().isForEval());
+        return false;
+    }
+
     static bool isArguments(JSContext *cx, jsid id)
     {
         return id == NameToId(cx->runtime->atomState.argumentsAtom);
     }
 
     static bool isFunctionScope(ScopeObject &scope)
     {
         return scope.isCall() && !scope.asCall().isForEval();
@@ -1280,24 +1419,24 @@ class DebugScopeProxy : public BaseProxy
 
         if (!isArguments(cx, id) || !isFunctionScope(scope))
             return true;
 
         JSScript *script = scope.asCall().getCalleeFunction()->script();
         if (script->needsArgsObj())
             return true;
 
-        StackFrame *fp = scope.maybeStackFrame();
-        if (!fp) {
+        StackFrame *maybefp = cx->runtime->debugScopes->hasLiveFrame(scope);
+        if (!maybefp) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NOT_LIVE,
                                  "Debugger scope");
             return false;
         }
 
-        *maybeArgsObj = ArgumentsObject::createUnexpected(cx, fp);
+        *maybeArgsObj = ArgumentsObject::createUnexpected(cx, maybefp);
         return true;
     }
 
   public:
     static int family;
     static DebugScopeProxy singleton;
 
     DebugScopeProxy() : BaseProxyHandler(&family) {}
@@ -1320,42 +1459,55 @@ class DebugScopeProxy : public BaseProxy
         if (maybeArgsObj) {
             PodZero(desc);
             desc->obj = proxy;
             desc->attrs = JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT;
             desc->value = ObjectValue(*maybeArgsObj);
             return true;
         }
 
-        AutoAllowUnaliasedVarAccess a(cx);
+        Value v;
+        if (handleUnaliasedAccess(cx, scope, id, GET, &v)) {
+            PodZero(desc);
+            desc->obj = proxy;
+            desc->attrs = JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT;
+            desc->value = v;
+            return true;
+        }
+
         return JS_GetPropertyDescriptorById(cx, &scope, id, JSRESOLVE_QUALIFIED, desc);
     }
 
     bool get(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, Value *vp) MOZ_OVERRIDE
     {
         ScopeObject &scope = proxy->asDebugScope().scope();
 
         ArgumentsObject *maybeArgsObj;
         if (!checkForMissingArguments(cx, id, scope, &maybeArgsObj))
             return false;
 
         if (maybeArgsObj) {
             *vp = ObjectValue(*maybeArgsObj);
             return true;
         }
 
-        AutoAllowUnaliasedVarAccess a(cx);
+        if (handleUnaliasedAccess(cx, scope, id, GET, vp))
+            return true;
+            
         return scope.getGeneric(cx, RootedObject(cx, &scope), RootedId(cx, id), vp);
     }
 
     bool set(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, bool strict,
                      Value *vp) MOZ_OVERRIDE
     {
-        AutoAllowUnaliasedVarAccess a(cx);
         ScopeObject &scope = proxy->asDebugScope().scope();
+
+        if (handleUnaliasedAccess(cx, scope, id, SET, vp))
+            return true;
+
         return scope.setGeneric(cx, RootedId(cx, id), vp, strict);
     }
 
     bool defineProperty(JSContext *cx, JSObject *proxy, jsid id, PropertyDescriptor *desc) MOZ_OVERRIDE
     {
         bool found;
         if (!has(cx, proxy, id, &found))
             return false;
@@ -1504,23 +1656,24 @@ DebugScopes::sweep()
      * creating an uncollectable cycle with suspended generator frames.
      */
     for (MissingScopeMap::Enum e(missingScopes); !e.empty(); e.popFront()) {
         if (!IsObjectMarked(&e.front().value))
             e.removeFront();
     }
 
     /*
-     * Since liveScopes includes entries for suspended generator frames which
-     * may be collected when the generator becomes unreachable we must sweep
-     * liveScopes for dead generator frames.
+     * Scopes can be finalized when a suspended generator becomes garbage or
+     * when a debugger-synthesized ScopeObject is no longer rooted by its
+     * DebugScopeObject.
      */
     for (LiveScopeMap::Enum e(liveScopes); !e.empty(); e.popFront()) {
-        if (JS_IsAboutToBeFinalized(e.front().key)) {
-            JS_ASSERT(e.front().value->isGeneratorFrame());
+        ScopeObject &scope = *e.front().key;
+        if (JS_IsAboutToBeFinalized(&scope)) {
+            JS_ASSERT(!scope.maybeStackFrame() || scope.maybeStackFrame()->isGeneratorFrame());
             e.removeFront();
         }
     }
 }
 
 /*
  * Unfortunately, GetDebugScopeForFrame needs to work even outside debug mode
  * (in particular, JS_GetFrameScopeChain does not require debug mode). Since
@@ -1544,16 +1697,17 @@ DebugScopes::hasDebugScope(JSContext *cx
     return NULL;
 }
 
 bool
 DebugScopes::addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope)
 {
     if (!CanUseDebugScopeMaps(cx))
         return true;
+
     JS_ASSERT(!proxiedScopes.has(&scope));
     if (!proxiedScopes.put(&scope, &debugScope)) {
         js_ReportOutOfMemory(cx);
         return false;
     }
     return true;
 }
 
@@ -1569,58 +1723,74 @@ DebugScopes::hasDebugScope(JSContext *cx
 }
 
 bool
 DebugScopes::addDebugScope(JSContext *cx, ScopeIter si, DebugScopeObject &debugScope)
 {
     JS_ASSERT(!si.hasScopeObject());
     if (!CanUseDebugScopeMaps(cx))
         return true;
+
     JS_ASSERT(!missingScopes.has(si));
     if (!missingScopes.put(si, &debugScope)) {
         js_ReportOutOfMemory(cx);
         return false;
     }
+
+    JS_ASSERT(!liveScopes.has(&debugScope.scope()));
+    if (!liveScopes.put(&debugScope.scope(), si.fp())) {
+        js_ReportOutOfMemory(cx);
+        return false;
+    }
     return true;
 }
 
 void
 DebugScopes::onPopCall(StackFrame *fp)
 {
     if (fp->isYielding())
         return;
 
     if (fp->fun()->isHeavyweight()) {
         /*
          * When a frame finishes executing in mjit code, the epilogue is called
          * once from the return and once when the frame is popped.
          * TODO: bug 659577 will remove this (with HAS_CALL_OBJ).
          */
-        if (fp->hasCallObj())
-            liveScopes.remove(&fp->scopeChain()->asCall());
+        if (fp->hasCallObj()) {
+            CallObject &callobj = fp->scopeChain()->asCall();
+            callobj.copyUnaliasedValues(fp);
+            liveScopes.remove(&callobj);
+        }
     } else {
         JS_ASSERT(!fp->hasCallObj());
         if (MissingScopeMap::Ptr p = missingScopes.lookup(ScopeIter(fp))) {
-            js_PutCallObject(fp, p->value->scope().asCall());
+            CallObject &callobj = p->value->scope().asCall();
+            callobj.copyUnaliasedValues(fp);
+            liveScopes.remove(&callobj);
             missingScopes.remove(p);
         }
     }
 }
 
 void
 DebugScopes::onPopBlock(JSContext *cx, StackFrame *fp)
 {
-    StaticBlockObject &block = *fp->maybeBlockChain();
-    if (block.needsClone()) {
-        liveScopes.remove(&fp->scopeChain()->asClonedBlock());
+    StaticBlockObject &staticBlock = *fp->maybeBlockChain();
+    if (staticBlock.needsClone()) {
+        ClonedBlockObject &clone = fp->scopeChain()->asClonedBlock();
+        clone.copyUnaliasedValues(fp);
+        liveScopes.remove(&clone);
     } else {
         JS_ASSERT(!fp->scopeChain()->isBlock() ||
-                  fp->scopeChain()->asClonedBlock().staticBlock() != block);
+                  fp->scopeChain()->asClonedBlock().staticBlock() != staticBlock);
         if (MissingScopeMap::Ptr p = missingScopes.lookup(ScopeIter(fp))) {
-            p->value->scope().asClonedBlock().put(fp);
+            ClonedBlockObject &clone = p->value->scope().asClonedBlock();
+            clone.copyUnaliasedValues(fp);
+            liveScopes.remove(&clone);
             missingScopes.remove(p);
         }
     }
 }
 
 void
 DebugScopes::onPopWith(StackFrame *fp)
 {
@@ -1648,25 +1818,17 @@ DebugScopes::onGeneratorFrameChange(Stac
             LiveScopeMap::AddPtr livePtr = liveScopes.lookupForAdd(&toIter.scope());
             if (livePtr)
                 livePtr->value = to;
             else
                 liveScopes.add(livePtr, &toIter.scope(), to);
         } else {
             if (MissingScopeMap::Ptr p = missingScopes.lookup(ScopeIter(toIter, from))) {
                 DebugScopeObject &debugScope = *p->value;
-                ScopeObject &scope = debugScope.scope();
-                if (scope.isCall()) {
-                    JS_ASSERT(scope.maybeStackFrame() == from);
-                    scope.setStackFrame(to);
-                    if (scope.enclosingScope().isDeclEnv()) {
-                        JS_ASSERT(scope.enclosingScope().asDeclEnv().maybeStackFrame() == from);
-                        scope.enclosingScope().asDeclEnv().setStackFrame(to);
-                    }
-                }
+                liveScopes.lookup(&debugScope.scope())->value = to;
                 missingScopes.remove(p);
                 missingScopes.put(toIter, &debugScope);
             }
         }
     }
 }
 
 void
@@ -1715,24 +1877,18 @@ DebugScopes::updateLiveScopes(JSContext 
     }
 
     return true;
 }
 
 StackFrame *
 DebugScopes::hasLiveFrame(ScopeObject &scope)
 {
-    if (LiveScopeMap::Ptr p = liveScopes.lookup(&scope)) {
-        JS_ASSERT_IF(scope.isClonedBlock(),
-                     p->value == js_LiveFrameIfGenerator(scope.maybeStackFrame()));
-        JS_ASSERT_IF(scope.isCall(),
-                     p->value == scope.maybeStackFrame());
+    if (LiveScopeMap::Ptr p = liveScopes.lookup(&scope))
         return p->value;
-    }
-    JS_ASSERT_IF(!scope.isWith(), !scope.maybeStackFrame());
     return NULL;
 }
 
 /*****************************************************************************/
 
 static JSObject *
 GetDebugScope(JSContext *cx, ScopeIter si);
 
@@ -1784,50 +1940,45 @@ GetDebugScopeForMissing(JSContext *cx, S
      */
     DebugScopeObject *debugScope = NULL;
     switch (si.type()) {
       case ScopeIter::Call: {
         CallObject *callobj = CallObject::createForFunction(cx, si.fp());
         if (!callobj)
             return NULL;
 
-        JSObject &maybeDecl = callobj->enclosingScope();
-        if (maybeDecl.isDeclEnv()) {
+        if (callobj->enclosingScope().isDeclEnv()) {
             JS_ASSERT(CallObjectLambdaName(callobj->getCalleeFunction()));
-            enclosingDebug = DebugScopeObject::create(cx, maybeDecl.asDeclEnv(), *enclosingDebug);
+            DeclEnvObject &declenv = callobj->enclosingScope().asDeclEnv();
+            declenv.setStackFrame(NULL);
+            enclosingDebug = DebugScopeObject::create(cx, declenv, *enclosingDebug);
             if (!enclosingDebug)
                 return NULL;
         }
 
+        callobj->setStackFrame(NULL);
         debugScope = DebugScopeObject::create(cx, *callobj, *enclosingDebug);
-        if (!debugScope)
-            return NULL;
-
-        if (!CanUseDebugScopeMaps(cx))
-            js_PutCallObject(si.fp(), *callobj);
         break;
       }
       case ScopeIter::Block: {
         Rooted<StaticBlockObject *> staticBlock(cx, &si.staticBlock());
         ClonedBlockObject *block = ClonedBlockObject::create(cx, staticBlock, si.fp());
         if (!block)
             return NULL;
 
+        block->setStackFrame(NULL);
         debugScope = DebugScopeObject::create(cx, *block, *enclosingDebug);
-        if (!debugScope)
-            return NULL;
-
-        if (!CanUseDebugScopeMaps(cx))
-            block->put(si.fp());
         break;
       }
       case ScopeIter::With:
       case ScopeIter::StrictEvalScope:
         JS_NOT_REACHED("should already have a scope");
     }
+    if (!debugScope)
+        return NULL;
 
     if (!debugScopes.addDebugScope(cx, si, *debugScope))
         return NULL;
 
     return debugScope;
 }
 
 static JSObject *
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -155,16 +155,19 @@ class CallObject : public ScopeObject
 
     static JSBool getArgOp(JSContext *cx, HandleObject obj, HandleId id, Value *vp);
     static JSBool getVarOp(JSContext *cx, HandleObject obj, HandleId id, Value *vp);
     static JSBool setArgOp(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, Value *vp);
     static JSBool setVarOp(JSContext *cx, HandleObject obj, HandleId id, JSBool strict, Value *vp);
 
     /* Return whether this environment contains 'name' and, if so, its value. */
     bool containsVarOrArg(PropertyName *name, Value *vp, JSContext *cx);
+
+    /* Copy in all the unaliased formals and locals. */
+    void copyUnaliasedValues(StackFrame *fp);
 };
 
 class DeclEnvObject : public ScopeObject
 {
   public:
     static const uint32_t RESERVED_SLOTS = 1;
     static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT2;
 
@@ -213,17 +216,18 @@ class BlockObject : public NestedScopeOb
     static const unsigned RESERVED_SLOTS = 2;
     static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4;
 
     /* Return the number of variables associated with this block. */
     inline uint32_t slotCount() const;
 
   protected:
     /* Blocks contain an object slot for each slot i: 0 <= i < slotCount. */
-    inline HeapSlot &slotValue(unsigned i);
+    inline const Value &slotValue(unsigned i);
+    inline void setSlotValue(unsigned i, const Value &v);
 };
 
 class StaticBlockObject : public BlockObject
 {
     /* These ScopeObject operations are not valid on a static block object. */
     StackFrame *maybeStackFrame() const;
     void setStackFrame(StackFrame *frame);
 
@@ -270,20 +274,24 @@ class ClonedBlockObject : public BlockOb
 
     /*
      * When this block's stack slots are about to be popped, 'put' must be
      * called to copy the slot values into this block's object slots.
      */
     void put(StackFrame *fp);
 
     /* Assuming 'put' has been called, return the value of the ith let var. */
-    const Value &closedSlot(unsigned i);
+    const Value &var(unsigned i);
+    void setVar(unsigned i, const Value &v);
 
     /* Return whether this environment contains 'name' and, if so, its value. */
     bool containsVar(PropertyName *name, Value *vp, JSContext *cx);
+
+    /* Copy in all the unaliased formals and locals. */
+    void copyUnaliasedValues(StackFrame *fp);
 };
 
 template<XDRMode mode>
 bool
 XDRStaticBlockObject(XDRState<mode> *xdr, JSScript *script, StaticBlockObject **objp);
 
 extern JSObject *
 CloneStaticBlockObject(JSContext *cx, StaticBlockObject &srcBlock,