Bug 785174: Implement Debugger.Object.prototype.evalInGlobal, .evalInGlobalWithBindings, .evalInGlobalWithBindingsAndABrandySoakedMaraschinoCherry r=jorendorff

This commit is contained in:
Jim Blandy 2012-09-07 20:08:14 -07:00
parent 0720b2f652
commit 758e5f46a4
7 changed files with 174 additions and 8 deletions

View File

@ -0,0 +1,13 @@
// Debugger.Object.prototype.evalInGlobal basics
var g = newGlobal('new-compartment');
var h = newGlobal('new-compartment');
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
var hw = dbg.addDebuggee(h);
g.y = "Bitte Orca";
h.y = "Visiter";
var y = "W H O K I L L";
assertEq(gw.evalInGlobal('y').return, "Bitte Orca");
assertEq(hw.evalInGlobal('y').return, "Visiter");

View File

@ -0,0 +1,20 @@
// Debugger.Object.prototype.evalInGlobal argument validation
load(libdir + 'asserts.js');
var g = newGlobal('new-compartment');
var dbg = new Debugger();
var gw = dbg.addDebuggee(g);
var gobj = gw.makeDebuggeeValue(g.eval("({})"));
assertThrowsInstanceOf(function () { gw.evalInGlobal(); }, TypeError);
assertThrowsInstanceOf(function () { gw.evalInGlobal(10); }, TypeError);
assertThrowsInstanceOf(function () { gobj.evalInGlobal('42'); }, TypeError);
assertEq(gw.evalInGlobal('42').return, 42);
assertThrowsInstanceOf(function () { gw.evalInGlobalWithBindings(); }, TypeError);
assertThrowsInstanceOf(function () { gw.evalInGlobalWithBindings('42'); }, TypeError);
assertThrowsInstanceOf(function () { gw.evalInGlobalWithBindings(10, 1729); }, TypeError);
assertThrowsInstanceOf(function () { gw.evalInGlobalWithBindings('42', 1729); }, TypeError);
assertThrowsInstanceOf(function () { gobj.evalInGlobalWithBindings('42', {}); }, TypeError);
assertEq(gw.evalInGlobalWithBindings('42', {}).return, 42);

View File

@ -0,0 +1,19 @@
// Debugger.Object.prototype.evalInGlobal: closures capturing the global
var g = newGlobal('new-compartment');
var h = newGlobal('new-compartment');
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
var hw = dbg.addDebuggee(h);
g.x = "W H O K I L L";
h.x = "No Color";
var c1 = gw.evalInGlobal('(function () { return x; })').return;
var c2 = hw.evalInGlobal('(function () { return x; })').return;
var c3 = gw.evalInGlobalWithBindings('(function () { return x + y; })', { y:" In Rainbows" }).return;
var c4 = hw.evalInGlobalWithBindings('(function () { return x + y; })', { y:" In Rainbows" }).return;
assertEq(c1.call(null).return, "W H O K I L L");
assertEq(c2.call(null).return, "No Color");
assertEq(c3.call(null).return, "W H O K I L L In Rainbows");
assertEq(c4.call(null).return, "No Color In Rainbows");

View File

@ -0,0 +1,55 @@
// Debugger.Object.prototype.evalInGlobal: nested evals
var g = newGlobal('new-compartment');
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
assertEq(gw.evalInGlobal("eval('\"Awake\"');").return, "Awake");
// Evaluating non-strict-mode code uses the given global as its variable
// environment.
g.x = "Swing Lo Magellan";
g.y = "The Milk-Eyed Mender";
assertEq(gw.evalInGlobal("eval('var x = \"A Brief History of Love\"');\n"
+ "var y = 'Merriweather Post Pavilion';"
+ "x;").return,
"A Brief History of Love");
assertEq(g.x, "A Brief History of Love");
assertEq(g.y, "Merriweather Post Pavilion");
// As above, but notice that we still create bindings on the global, even
// when we've interposed a new environment via 'withBindings'.
g.x = "Swing Lo Magellan";
g.y = "The Milk-Eyed Mender";
assertEq(gw.evalInGlobalWithBindings("eval('var x = d1;'); var y = d2; x;",
{ d1: "A Brief History of Love",
d2: "Merriweather Post Pavilion" }).return,
"A Brief History of Love");
assertEq(g.x, "A Brief History of Love");
assertEq(g.y, "Merriweather Post Pavilion");
// Strict mode code variants of the above:
// Evaluating strict-mode code uses a fresh call object as its variable environment.
// Also, calls to eval from strict-mode code run the eval code in a fresh
// call object.
g.x = "Swing Lo Magellan";
g.y = "The Milk-Eyed Mender";
assertEq(gw.evalInGlobal("\'use strict\';\n"
+ "eval('var x = \"A Brief History of Love\"');\n"
+ "var y = \"Merriweather Post Pavilion\";"
+ "x;").return,
"Swing Lo Magellan");
assertEq(g.x, "Swing Lo Magellan");
assertEq(g.y, "The Milk-Eyed Mender");
// Introducing a bindings object shouldn't change this behavior.
g.x = "Swing Lo Magellan";
g.y = "The Milk-Eyed Mender";
assertEq(gw.evalInGlobalWithBindings("'use strict'; eval('var x = d1;'); var y = d2; x;",
{ d1: "A Brief History of Love",
d2: "Merriweather Post Pavilion" }).return,
"Swing Lo Magellan");
assertEq(g.x, "Swing Lo Magellan");
assertEq(g.y, "The Milk-Eyed Mender");

View File

@ -375,3 +375,4 @@ MSG_DEF(JSMSG_MUST_REPORT_SAME_VALUE, 321, 0, JSEXN_TYPEERR, "proxy must report
MSG_DEF(JSMSG_MUST_REPORT_UNDEFINED, 322, 0, JSEXN_TYPEERR, "proxy must report undefined for a non-configurable accessor property without a getter")
MSG_DEF(JSMSG_CANT_SET_NW_NC, 323, 0, JSEXN_TYPEERR, "proxy can't successfully set a non-writable, non-configurable property")
MSG_DEF(JSMSG_CANT_SET_WO_SETTER, 324, 0, JSEXN_TYPEERR, "proxy can't succesfully set an accessor property without a setter")
MSG_DEF(JSMSG_DEBUG_BAD_REFERENT, 325, 2, JSEXN_TYPEERR, "{0} does not refer to {1}")

View File

@ -81,7 +81,7 @@ ReportMoreArgsNeeded(JSContext *cx, const char *name, unsigned required)
s[0] = '0' + (required - 1);
s[1] = '\0';
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED,
name, s, required == 1 ? "" : "s");
name, s, required == 2 ? "" : "s");
return false;
}
@ -3399,10 +3399,14 @@ js::EvaluateInEnv(JSContext *cx, Handle<Env*> env, StackFrame *fp, const jschar
JS_ASSERT(!IsPoisonedPtr(chars));
SkipRoot skip(cx, &chars);
RootedValue thisv(cx);
if (fp) {
/* Execute assumes an already-computed 'this" value. */
if (!ComputeThis(cx, fp))
return false;
thisv = fp->thisValue();
} else {
thisv = ObjectValue(*env);
}
/*
@ -3411,17 +3415,18 @@ js::EvaluateInEnv(JSContext *cx, Handle<Env*> env, StackFrame *fp, const jschar
* static level will suffice.
*/
CompileOptions options(cx);
options.setPrincipals(fp->scopeChain()->compartment()->principals)
options.setPrincipals(env->compartment()->principals)
.setCompileAndGo(true)
.setNoScriptRval(false)
.setFileAndLine(filename, lineno);
RootedScript script(cx, frontend::CompileScript(cx, env, fp, options, chars, length,
/* source = */ NULL, /* staticLimit = */ 1));
/* source = */ NULL,
/* staticLevel = */ fp ? 1 : 0));
if (!script)
return false;
script->isActiveEval = true;
return ExecuteKernel(cx, script, *env, fp->thisValue(), EXECUTE_DEBUG, fp, rval);
return ExecuteKernel(cx, script, *env, thisv, EXECUTE_DEBUG, fp, rval);
}
static JSBool
@ -3469,10 +3474,14 @@ DebuggerGenericEval(JSContext *cx, const char *fullMethodName,
}
}
Maybe<AutoCompartment> ac;
ac.construct(cx, fp->scopeChain());
Rooted<Env *> env(cx, GetDebugScopeForFrame(cx, fp));
Maybe<AutoCompartment> ac;
if (fp)
ac.construct(cx, fp->scopeChain());
else
ac.construct(cx, scope);
Rooted<Env *> env(cx, fp ? GetDebugScopeForFrame(cx, fp) : scope);
if (!env)
return false;
@ -4196,6 +4205,43 @@ DebuggerObject_makeDebuggeeValue(JSContext *cx, unsigned argc, Value *vp)
return true;
}
static bool
RequireGlobalObject(JSContext *cx, HandleValue dbgobj, HandleObject obj)
{
if (!obj->isGlobal()) {
js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_DEBUG_BAD_REFERENT,
JSDVG_SEARCH_STACK, dbgobj, NullPtr(),
"a global object", NULL);
return false;
}
return true;
}
static JSBool
DebuggerObject_evalInGlobal(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Object.prototype.evalInGlobal", 1);
THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "evalInGlobal", args, dbg, referent);
if (!RequireGlobalObject(cx, args.thisv(), referent))
return false;
return DebuggerGenericEval(cx, "Debugger.Object.prototype.evalInGlobal",
args[0], NULL, vp, dbg, referent, NULL);
}
static JSBool
DebuggerObject_evalInGlobalWithBindings(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Object.prototype.evalInGlobalWithBindings", 2);
THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "evalInGlobalWithBindings", args, dbg, referent);
if (!RequireGlobalObject(cx, args.thisv(), referent))
return false;
return DebuggerGenericEval(cx, "Debugger.Object.prototype.evalInGlobalWithBindings",
args[0], &args[1], vp, dbg, referent, NULL);
}
static JSPropertySpec DebuggerObject_properties[] = {
JS_PSG("proto", DebuggerObject_getProto, 0),
JS_PSG("class", DebuggerObject_getClass, 0),
@ -4223,6 +4269,8 @@ static JSFunctionSpec DebuggerObject_methods[] = {
JS_FN("apply", DebuggerObject_apply, 0, 0),
JS_FN("call", DebuggerObject_call, 0, 0),
JS_FN("makeDebuggeeValue", DebuggerObject_makeDebuggeeValue, 1, 0),
JS_FN("evalInGlobal", DebuggerObject_evalInGlobal, 1, 0),
JS_FN("evalInGlobalWithBindings", DebuggerObject_evalInGlobalWithBindings, 2, 0),
JS_FS_END
};

View File

@ -321,6 +321,16 @@ StackFrame::epilogue(JSContext *cx)
else
JS_ASSERT(scopeChain() == prev()->scopeChain());
} else {
/*
* Debugger.Object.prototype.evalInGlobal creates indirect eval
* frames scoped to the given global;
* Debugger.Object.prototype.evalInGlobalWithBindings creates
* indirect eval frames scoped to an object carrying the introduced
* bindings.
*/
if (isDebuggerFrame())
JS_ASSERT(scopeChain()->isGlobal() || scopeChain()->enclosingScope()->isGlobal());
else
JS_ASSERT(scopeChain()->isGlobal());
}
return;