Add Debug.Frame.prototype.evalWithBindings.
authorJason Orendorff <jorendorff@mozilla.com>
Wed, 25 May 2011 15:21:53 -0500
changeset 74456 d249478fbbe1828131c1d7388dfb361378e501fe
parent 74455 a34a276b47c5c0bd6ecb1391c6500b201bc9134d
child 74457 614e714d02981477663bdfd58f30bf5aa34a936b
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
milestone6.0a1
Add Debug.Frame.prototype.evalWithBindings.
js/src/jit-test/tests/debug/Frame-evalWithBindings-01.js
js/src/jit-test/tests/debug/Frame-evalWithBindings-02.js
js/src/jit-test/tests/debug/Frame-evalWithBindings-03.js
js/src/jit-test/tests/debug/Frame-evalWithBindings-04.js
js/src/jit-test/tests/debug/Frame-evalWithBindings-05.js
js/src/jit-test/tests/debug/Frame-evalWithBindings-06.js
js/src/jit-test/tests/debug/Frame-evalWithBindings-07.js
js/src/jit-test/tests/debug/Frame-evalWithBindings-08.js
js/src/jit-test/tests/debug/Frame-evalWithBindings-09.js
js/src/jit-test/tests/debug/Frame-evalWithBindings-10.js
js/src/jsdbg.cpp
js/src/jsdbg.h
js/src/jsdbgapi.cpp
js/src/jsinterp.cpp
js/src/vm/Stack-inl.h
js/src/vm/Stack.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-01.js
@@ -0,0 +1,38 @@
+// |jit-test| debug
+// evalWithBindings basics
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+	assertEq(frame.evalWithBindings("x", {x: 2}).return, 2);
+	assertEq(frame.evalWithBindings("x + y", {x: 2}).return, 5);
+	hits++;
+    }
+};
+
+// in global code
+g.y = 3;
+g.eval("debugger;");
+
+// in function code
+g.y = "fail";
+g.eval("function f(y) { debugger; }");
+g.f(3);
+
+// in direct eval code
+g.eval("function f() { var y = 3; eval('debugger;'); }");
+g.f();
+
+// in strict eval code with var
+g.eval("function f() { 'use strict'; eval('var y = 3; debugger;'); }");
+g.f();
+
+// in a with block
+g.eval("with ({y: 3}) { debugger; }");
+
+// shadowing
+g.eval("let (x = 50, y = 3) { debugger; }");
+
+assertEq(hits, 6);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-02.js
@@ -0,0 +1,22 @@
+// |jit-test| debug
+// evalWithBindings to call a method of a debuggee object
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug;
+var global = dbg.addDebuggee(g);
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+	var obj = frame.arguments[0];
+	var expected = frame.arguments[1];
+	assertEq(frame.evalWithBindings("obj.toString()", {obj: obj}).return, expected);
+	hits++;
+    }
+};
+
+g.eval("function f(obj, expected) { debugger; }");
+
+g.eval("f(new Number(-0), '0');");
+g.eval("f(new String('ok'), 'ok');");
+g.eval("f({toString: function () { return f; }}, f);");
+assertEq(hits, 3);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-03.js
@@ -0,0 +1,20 @@
+// |jit-test| debug
+// arguments works in evalWithBindings (it does not interpose a function scope)
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug;
+var global = dbg.addDebuggee(g);
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+	var argc = frame.arguments.length;
+	assertEq(argc, 7);
+	assertEq(frame.evalWithBindings("arguments[prop]", {prop: "length"}).return, argc);
+	for (var i = 0; i < argc; i++)
+	    assertEq(frame.evalWithBindings("arguments[i]", {i: i}).return, frame.arguments[i]);
+	hits++;
+    }
+};
+g.eval("function f() { debugger; }");
+g.eval("f(undefined, -0, NaN, '\uffff', Array.prototype, Math, f);");
+assertEq(hits, 1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-04.js
@@ -0,0 +1,21 @@
+// |jit-test| debug
+// evalWithBindings works on non-top frames.
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var f1;
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+	assertEq(frame.older.evalWithBindings("q + r", {r: 3}).return, 5);
+
+	// frame.older.older is in the same function as frame, but a different activation of it
+	assertEq(frame.older.older.evalWithBindings("q + r", {r: 3}).return, 6);
+	hits++;
+    }
+};
+
+g.eval("function f1(q) { if (q == 1) debugger; else f2(2); }");
+g.eval("function f2(arg) { var q = arg; f1(1); }");
+g.f1(3);
+assertEq(hits, 1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-05.js
@@ -0,0 +1,15 @@
+// |jit-test| debug
+// evalWithBindings code can assign to the bindings.
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+	assertEq(frame.evalWithBindings("for (i = 0; i < 5; i++) {}  i;", {i: 10}).return, 5);
+	hits++;
+    }
+};
+
+g.eval("debugger;");
+assertEq("i" in g, false);
+assertEq(hits, 1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-06.js
@@ -0,0 +1,13 @@
+// |jit-test| debug
+// In evalWithBindings code, assignment to any name not in the bindings works just as in eval.
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+	assertEq(frame.evalWithBindings("y = z; x = w;", {z: 2, w: 3}).return, 3);
+    }
+};
+g.eval("function f(x) { debugger; assertEq(x, 3); }");
+g.eval("var y = 0; f(0);");
+assertEq(g.y, 2);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-07.js
@@ -0,0 +1,20 @@
+// |jit-test| debug
+// var statements in strict evalWithBindings code behave like strict eval.
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+	assertEq(frame.evalWithBindings("var i = a*a + b*b; i === 25;", {a: 3, b: 4}).return, true);
+	hits++;
+    }
+};
+g.eval("'use strict'; debugger;");
+assertEq(hits, 1);
+assertEq("i" in g, false);
+
+g.eval("function die() { throw fit; }");
+g.eval("Object.defineProperty(this, 'i', {get: die, set: die});");
+g.eval("'use strict'; debugger;");
+assertEq(hits, 2);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-08.js
@@ -0,0 +1,17 @@
+// |jit-test| debug
+// evalWithBindings ignores non-enumerable and non-own properties.
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+	assertEq(frame.evalWithBindings("toString + constructor + length", []).return, 112233);
+	var obj = Object.create({constructor: "FAIL"}, {length: {value: "fail"}});
+	assertEq(frame.evalWithBindings("toString + constructor + length", obj).return, 112233);
+	hits++;
+    }
+};
+g.eval("function f() { var toString = 111111, constructor = 1111, length = 11; debugger; }");
+g.f();
+assertEq(hits, 1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-09.js
@@ -0,0 +1,31 @@
+// |jit-test| debug
+// evalWithBindings code is debuggee code, so it can trip the debugger. It nests!
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var f1;
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+	f1 = frame;
+
+	// This trips the throw hook.
+	var x = frame.evalWithBindings("wrongSpeling", {rightSpelling: 2}).throw;
+
+	assertEq(frame.evalWithBindings("exc.name", {exc: x}).return, "ReferenceError");
+	hits++;
+    },
+    throw: function (frame, exc) {
+	assertEq(frame !== f1, true);
+
+	// f1's environment does not contain the binding for the first evalWithBindings call.
+	assertEq(f1.eval("rightSpelling").return, "dependent");
+	assertEq(f1.evalWithBindings("n + rightSpelling", {n: "in"}).return, "independent");
+
+	// frame's environment does contain the binding.
+	assertEq(frame.eval("rightSpelling").return, 2);
+	assertEq(frame.evalWithBindings("rightSpelling + three", {three: 3}).return, 5);
+	hits++;
+    }
+};
+g.eval("(function () { var rightSpelling = 'dependent'; debugger; })();");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-10.js
@@ -0,0 +1,19 @@
+// |jit-test| debug
+// Direct eval code under evalWithbindings sees both the bindings and the enclosing scope.
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+	var code =
+	    "assertEq(a, 1234);\n" +
+	    "assertEq(b, null);\n" +
+	    "assertEq(c, 'ok');\n";
+	assertEq(frame.evalWithBindings("eval(s)", {s: code, a: 1234}).return, undefined);
+	hits++;
+    }
+};
+g.eval("function f(b) { var c = 'ok'; debugger; }");
+g.f(null);
+assertEq(hits, 1);
--- a/js/src/jsdbg.cpp
+++ b/js/src/jsdbg.cpp
@@ -37,16 +37,17 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "jsdbg.h"
 #include "jsapi.h"
 #include "jscntxt.h"
+#include "jsemit.h"
 #include "jsgcmark.h"
 #include "jsobj.h"
 #include "jswrapper.h"
 #include "jsinterpinlines.h"
 #include "jsobjinlines.h"
 #include "vm/Stack-inl.h"
 
 using namespace js;
@@ -90,17 +91,17 @@ ReportMoreArgsNeeded(JSContext *cx, cons
     s[1] = '\0';
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED,
                          name, s, required == 1 ? "" : "s");
     return false;
 }
 
 #define REQUIRE_ARGC(name, n) \
     JS_BEGIN_MACRO \
-        if (argc < n) \
+        if (argc < (n)) \
             return ReportMoreArgsNeeded(cx, name, n); \
     JS_END_MACRO
 
 bool
 ReportObjectRequired(JSContext *cx)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_NONNULL_OBJECT);
     return false;
@@ -1090,17 +1091,18 @@ CheckThisFrame(JSContext *cx, Value *vp,
     }
     return thisobj;
 }
 
 #define THIS_FRAME(cx, vp, fnname, thisobj, fp)                              \
     JSObject *thisobj = CheckThisFrame(cx, vp, fnname, true);                \
     if (!thisobj)                                                            \
         return false;                                                        \
-    StackFrame *fp = (StackFrame *) thisobj->getPrivate()
+    StackFrame *fp = (StackFrame *) thisobj->getPrivate();                   \
+    JS_ASSERT((cx)->stack.contains(fp))
 
 static JSBool
 DebugFrame_getType(JSContext *cx, uintN argc, Value *vp)
 {
     THIS_FRAME(cx, vp, "get type", thisobj, fp);
 
     // Indirect eval frames are both isGlobalFrame() and isEvalFrame(), so the
     // order of checks here is significant.
@@ -1263,43 +1265,135 @@ DebugFrame_getLive(JSContext *cx, uintN 
     JSObject *thisobj = CheckThisFrame(cx, vp, "get live", false);
     if (!thisobj)
         return false;
     StackFrame *fp = (StackFrame *) thisobj->getPrivate();
     vp->setBoolean(!!fp);
     return true;
 }
 
-static JSBool
-DebugFrame_eval(JSContext *cx, uintN argc, Value *vp)
+namespace js {
+
+JSBool
+EvaluateInScope(JSContext *cx, JSObject *scobj, StackFrame *fp, const jschar *chars,
+                uintN length, const char *filename, uintN lineno, Value *rval)
 {
-    REQUIRE_ARGC("Debug.Frame.eval", 1);
-    THIS_FRAME(cx, vp, "eval", thisobj, fp);
+    assertSameCompartment(cx, scobj, fp);
+
+    /*
+     * NB: This function breaks the assumption that the compiler can see all
+     * calls and properly compute a static level. In order to get around this,
+     * we use a static level that will cause us not to attempt to optimize
+     * variable references made by this frame.
+     */
+    JSScript *script = Compiler::compileScript(cx, scobj, fp, fp->scopeChain().principals(cx),
+                                               TCF_COMPILE_N_GO, chars, length,
+                                               filename, lineno, cx->findVersion(),
+                                               NULL, UpvarCookie::UPVAR_LEVEL_LIMIT);
+
+    if (!script)
+        return false;
+
+    bool ok = Execute(cx, *scobj, script, fp, StackFrame::DEBUGGER | StackFrame::EVAL, rval);
+    js_DestroyScript(cx, script);
+    return ok;
+}
+
+}
+
+enum EvalBindingsMode { WithoutBindings, WithBindings };
+
+static JSBool
+DebugFrameEval(JSContext *cx, uintN argc, Value *vp, EvalBindingsMode mode)
+{
+    REQUIRE_ARGC(mode == WithBindings ? "Debug.Frame.evalWithBindings" : "Debug.Frame.eval",
+                 mode == WithBindings ? 2 : 1);
+    THIS_FRAME(cx, vp, mode == WithBindings ? "evalWithBindings" : "eval", thisobj, fp);
     Debug *dbg = Debug::fromChildJSObject(&vp[1].toObject());
 
+    // Check the first argument, the eval code string.
     if (!vp[2].isString()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_EXPECTED_TYPE,
                              "Debug.Frame.eval", "string", InformalValueTypeName(vp[2]));
         return false;
     }
     JSLinearString *linearStr = vp[2].toString()->ensureLinear(cx);
     if (!linearStr)
         return false;
-    JS::Anchor<JSString *> anchor(linearStr);
+
+    // Gather keys and values of bindings, if any. This must be done in the
+    // debugger compartment, since that is where any exceptions must be
+    // thrown.
+    AutoIdVector keys(cx);
+    AutoValueVector values(cx);
+    if (mode == WithBindings) {
+        JSObject *bindingsobj = NonNullObject(cx, vp[3]);
+        if (!bindingsobj ||
+            !GetPropertyNames(cx, bindingsobj, JSITER_OWNONLY, &keys) ||
+            !values.growBy(keys.length()))
+        {
+            return false;
+        }
+        for (size_t i = 0; i < keys.length(); i++) {
+            Value *vp = &values[i];
+            if (!bindingsobj->getProperty(cx, bindingsobj, keys[i], vp) ||
+                !dbg->unwrapDebuggeeValue(cx, vp))
+            {
+                return false;
+            }
+        }
+    }
 
     AutoCompartment ac(cx, &fp->scopeChain());
     if (!ac.enter())
         return false;
+
+    // Get a scope object.
+    if (fp->isNonEvalFunctionFrame() && !fp->hasCallObj() && !CreateFunCallObject(cx, fp))
+        return false;
+    JSObject *scobj = GetScopeChain(cx, fp);
+    if (!scobj)
+        return false;
+
+    // If evalWithBindings, create the inner scope object.
+    if (mode == WithBindings) {
+        // TODO - Should probably create a With object here.
+        scobj = NewNonFunction<WithProto::Given>(cx, &js_ObjectClass, NULL, scobj);
+        if (!scobj)
+            return false;
+        for (size_t i = 0; i < keys.length(); i++) {
+            if (!cx->compartment->wrap(cx, &values[i]) ||
+                !DefineNativeProperty(cx, scobj, keys[i], values[i], NULL, NULL, 0, 0, 0))
+            {
+                return false;
+            }
+        }
+    }
+
+    // Run the code and produce the completion value.
     Value rval;
-    bool ok = JS_EvaluateUCInStackFrame(cx, Jsvalify(fp), linearStr->chars(), linearStr->length(),
-                                        "debugger eval code", 1, Jsvalify(&rval));
+    JS::Anchor<JSString *> anchor(linearStr);
+    bool ok = EvaluateInScope(cx, scobj, fp, linearStr->chars(), linearStr->length(),
+                              "debugger eval code", 1, &rval);
     return dbg->newCompletionValue(ac, ok, rval, vp);
 }
 
 static JSBool
+DebugFrame_eval(JSContext *cx, uintN argc, Value *vp)
+{
+    return DebugFrameEval(cx, argc, vp, WithoutBindings);
+}
+
+static JSBool
+DebugFrame_evalWithBindings(JSContext *cx, uintN argc, Value *vp)
+{
+    return DebugFrameEval(cx, argc, vp, WithBindings);
+}
+
+static JSBool
 DebugFrame_construct(JSContext *cx, uintN argc, Value *vp)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_CONSTRUCTOR, "Debug.Frame");
     return false;
 }
 
 static JSPropertySpec DebugFrame_properties[] = {
     JS_PSG("type", DebugFrame_getType, 0),
@@ -1310,16 +1404,17 @@ static JSPropertySpec DebugFrame_propert
     JS_PSG("generator", DebugFrame_getGenerator, 0),
     JS_PSG("constructing", DebugFrame_getConstructing, 0),
     JS_PSG("arguments", DebugFrame_getArguments, 0),
     JS_PS_END
 };
 
 static JSFunctionSpec DebugFrame_methods[] = {
     JS_FN("eval", DebugFrame_eval, 1, 0),
+    JS_FN("evalWithBindings", DebugFrame_evalWithBindings, 1, 0),
     JS_FS_END
 };
 
 
 // === Debug.Object
 
 Class DebugObject_class = {
     "Object", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGOBJECT_COUNT),
--- a/js/src/jsdbg.h
+++ b/js/src/jsdbg.h
@@ -265,11 +265,15 @@ Debug::onThrow(JSContext *cx, js::Value 
 {
     return cx->compartment->getDebuggees().empty()
            ? JSTRAP_CONTINUE
            : dispatchHook(cx, vp,
                           DebugObservesMethod(&Debug::observesThrow),
                           DebugHandleMethod(&Debug::handleThrow));
 }
 
+extern JSBool
+EvaluateInScope(JSContext *cx, JSObject *scobj, StackFrame *fp, const jschar *chars,
+                uintN length, const char *filename, uintN lineno, Value *rval);
+
 }
 
 #endif /* jsdbg_h__ */
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -1678,36 +1678,18 @@ JS_EvaluateUCInStackFrame(JSContext *cx,
     JSObject *scobj = JS_GetFrameScopeChain(cx, fpArg);
     if (!scobj)
         return false;
 
     js::AutoCompartment ac(cx, scobj);
     if (!ac.enter())
         return false;
 
-    /*
-     * NB: This function breaks the assumption that the compiler can see all
-     * calls and properly compute a static level. In order to get around this,
-     * we use a static level that will cause us not to attempt to optimize
-     * variable references made by this frame.
-     */
     StackFrame *fp = Valueify(fpArg);
-    JSScript *script = Compiler::compileScript(cx, scobj, fp, fp->scopeChain().principals(cx),
-                                               TCF_COMPILE_N_GO, chars, length,
-                                               filename, lineno, cx->findVersion(),
-                                               NULL, UpvarCookie::UPVAR_LEVEL_LIMIT);
-
-    if (!script)
-        return false;
-
-    uintN evalFlags = StackFrame::DEBUGGER | StackFrame::EVAL;
-    bool ok = Execute(cx, *scobj, script, fp, evalFlags, Valueify(rval));
-
-    js_DestroyScript(cx, script);
-    return ok;
+    return EvaluateInScope(cx, scobj, fp, chars, length, filename, lineno, Valueify(rval));
 }
 
 JS_PUBLIC_API(JSBool)
 JS_EvaluateInStackFrame(JSContext *cx, JSStackFrame *fp,
                         const char *bytes, uintN length,
                         const char *filename, uintN lineno,
                         jsval *rval)
 {
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -899,18 +899,17 @@ Execute(JSContext *cx, JSObject &chain, 
         return false;
 
     /* Initialize fixed slots (GVAR ops expect NULL). */
     SetValueRangeToNull(frame.fp()->slots(), script->nfixed);
 
     /* Initialize frame and locals. */
     JSObject *initialVarObj;
     if (prev) {
-        JS_ASSERT(chain == prev->scopeChain());
-        frame.fp()->initEvalFrame(cx, script, prev, flags);
+        frame.fp()->initEvalFrame(cx, script, prev, &chain, flags);
 
         /* NB: prev may not be in cx->currentSegment. */
         initialVarObj = (prev == cx->maybefp())
                         ? &cx->stack.currentVarObj()
                         : &cx->stack.space().varObjForFrame(prev);
     } else {
         /* The scope chain could be anything, so innerize just in case. */
         JSObject *innerizedChain = &chain;
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -396,22 +396,29 @@ StackFrame::initCallFrameEarlyPrologue(J
  */
 inline void
 StackFrame::initCallFrameLatePrologue()
 {
     SetValueRangeToUndefined(slots(), script()->nfixed);
 }
 
 inline void
-StackFrame::initEvalFrame(JSContext *cx, JSScript *script, StackFrame *prev, uint32 flagsArg)
+StackFrame::initEvalFrame(JSContext *cx, JSScript *script, StackFrame *prev, JSObject *chain, uint32 flagsArg)
 {
     JS_ASSERT(flagsArg & EVAL);
     JS_ASSERT((flagsArg & ~(EVAL | DEBUGGER)) == 0);
     JS_ASSERT(prev->isScriptFrame());
 
+    /*
+     * eval code always runs in prev's scope, except when executed via
+     * DebugFrame_evalWithBindings. Strict eval is another special case, dealt
+     * with specially in js::Execute after this method returns.
+     */
+    JS_ASSERT_IF(!(flagsArg & DEBUGGER), chain == &prev->scopeChain());
+
     /* Copy (callee, thisv). */
     Value *dstvp = (Value *)this - 2;
     Value *srcvp = prev->hasArgs()
                    ? prev->formalArgs() - 2
                    : (Value *)prev - 2;
     dstvp[0] = srcvp[0];
     dstvp[1] = srcvp[1];
     JS_ASSERT_IF(prev->isFunctionFrame(),
@@ -422,17 +429,17 @@ StackFrame::initEvalFrame(JSContext *cx,
              (prev->flags_ & (FUNCTION | GLOBAL));
     if (isFunctionFrame()) {
         exec = prev->exec;
         args.script = script;
     } else {
         exec.script = script;
     }
 
-    scopeChain_ = &prev->scopeChain();
+    scopeChain_ = chain;
     prev_ = prev;
     prevpc_ = prev->pc(cx);
     JS_ASSERT(!hasImacropc());
     JS_ASSERT(!hasHookData());
     setAnnotation(prev->annotation());
 }
 
 inline void
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -303,17 +303,17 @@ class StackFrame
     inline void resetInvokeCallFrame();
 
     /* Called by method-jit stubs and serve as a specification for jit-code. */
     inline void initCallFrameCallerHalf(JSContext *cx, uint32 flags, void *ncode);
     inline void initCallFrameEarlyPrologue(JSFunction *fun, uint32 nactual);
     inline void initCallFrameLatePrologue();
 
     /* Used for eval. */
-    inline void initEvalFrame(JSContext *cx, JSScript *script, StackFrame *prev,
+    inline void initEvalFrame(JSContext *cx, JSScript *script, StackFrame *prev, JSObject *chain,
                               uint32 flags);
     inline void initGlobalFrame(JSScript *script, JSObject &chain, StackFrame *prev,
                                 uint32 flags);
 
     /* Used when activating generators. */
     inline void stealFrameAndSlots(js::Value *vp, StackFrame *otherfp,
                                    js::Value *othervp, js::Value *othersp);