Debugger.Environment.getVariable and setVariable. Bug 692984, r=jimb.

--HG--
extra : rebase_source : fb68abbd27b3ca0809b20d99411ac797b337d227
This commit is contained in:
Jason Orendorff 2012-04-06 16:48:38 -05:00
Родитель de3d390e50
Коммит 0b48d9ec48
29 изменённых файлов: 549 добавлений и 4 удалений

Просмотреть файл

@ -0,0 +1,14 @@
// Environment.prototype.getVariable does not see variables bound in enclosing scopes.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var hits = 0;
dbg.onDebuggerStatement = function (frame) {
assertEq(frame.environment.getVariable("x"), 13);
assertEq(frame.environment.getVariable("k"), undefined);
assertEq(frame.environment.find("k").getVariable("k"), 3);
hits++;
};
g.eval("var k = 3; function f(x) { debugger; }");
g.f(13);
assertEq(hits, 1);

Просмотреть файл

@ -0,0 +1,16 @@
// getVariable works in function scopes.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var hits = 0;
dbg.onDebuggerStatement = function (frame) {
assertEq(frame.environment.getVariable("a"), 1);
assertEq(frame.environment.getVariable("b"), 2);
assertEq(frame.environment.getVariable("c"), 3);
assertEq(frame.environment.getVariable("d"), 4);
assertEq(frame.environment.getVariable("e"), 5);
hits++;
};
g.eval("function f(a, [b, c]) { var d = c + 1; let e = d + 1; debugger; }");
g.f(1, [2, 3]);
assertEq(hits, 1);

Просмотреть файл

@ -0,0 +1,22 @@
// getVariable sees bindings in let-block scopes.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var log = '';
dbg.onDebuggerStatement = function (frame) {
log += frame.environment.getVariable("x");
};
g.eval("function f() {\n" +
" let x = 'a';\n" +
" debugger;\n" +
" for (let x = 0; x < 2; x++)\n" +
" if (x === 0)\n" +
" debugger;\n" +
" else {\n" +
" let x = 'b'; debugger;\n" +
" }\n" +
"}\n");
g.f();
g.eval("let (x = 'c') { debugger; }");
g.eval("{ let x = 'd'; debugger; }");
assertEq(log, 'a0bcd');

Просмотреть файл

@ -0,0 +1,12 @@
// getVariable sees variables in function scopes added by non-strict direct eval.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var v;
dbg.onDebuggerStatement = function (frame) {
v = frame.environment.getVariable("x");
};
g.eval("function f(s) { eval(s); debugger; }");
g.f("var x = 'Q';");
assertEq(v, 'Q');

Просмотреть файл

@ -0,0 +1,10 @@
// getVariable sees global variables.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var log = '';
dbg.onDebuggerStatement = function (frame) {
log += frame.environment.getVariable("x") + frame.environment.getVariable("y");
};
g.eval("var x = 'a'; this.y = 'b'; debugger;");
assertEq(log, 'ab');

Просмотреть файл

@ -0,0 +1,12 @@
// getVariable sees properties inherited from the global object's prototype chain.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var log = '';
dbg.onDebuggerStatement = function (frame) {
log += frame.environment.getVariable("x") + frame.environment.getVariable("y");
};
g.eval("Object.getPrototypeOf(this).x = 'a';\n" +
"Object.prototype.y = 'b';\n" +
"debugger;\n");
assertEq(log, 'ab');

Просмотреть файл

@ -0,0 +1,10 @@
// getVariable can get properties from with-block scopes.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var v;
dbg.onDebuggerStatement = function (frame) {
v = frame.environment.getVariable("x");
};
g.eval("var x = 1; { let x = 2; with ({x: 3}) { debugger; } }");
assertEq(v, 3);

Просмотреть файл

@ -0,0 +1,10 @@
// getVariable sees properties inherited from a with-object's prototype chain.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var v;
dbg.onDebuggerStatement = function (frame) {
v = frame.environment.getVariable("x");
};
g.eval("var x = 1; { let x = 2; with (Object.create({x: 3})) { debugger; } }");
assertEq(v, 3);

Просмотреть файл

@ -0,0 +1,13 @@
// getVariable works on ancestors of frame.environment.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var log = '';
dbg.onDebuggerStatement = function (frame) {
for (var env = frame.environment; env; env = env.parent) {
if (env.find("x") === env)
log += env.getVariable("x");
}
};
g.eval("var x = 1; { let x = 2; with (Object.create({x: 3})) { debugger; } }");
assertEq(log, "321");

Просмотреть файл

@ -0,0 +1,27 @@
// getVariable works on a heavyweight environment after control leaves its scope.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var envs = [];
dbg.onDebuggerStatement = function (frame) {
envs.push(frame.environment);
};
g.eval("var f;\n" +
"for (var x = 0; x < 3; x++) {\n" +
" (function (x) {\n" +
" for (var y = 0; y < 3; y++) {\n" +
" (function (z) {\n" +
" eval(z); // force heavyweight\n" +
" debugger;\n" +
" })(x + y);\n" +
" }\n" +
" })(x);\n" +
"}");
var i = 0;
for (var x = 0; x < 3; x++) {
for (var y = 0; y < 3; y++) {
var e = envs[i++];
assertEq(e.getVariable("z"), x + y);
}
}

Просмотреть файл

@ -0,0 +1,15 @@
// The value returned by getVariable can be a Debugger.Object.
var g = newGlobal('new-compartment');
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
var hits = 0;
dbg.onDebuggerStatement = function (frame) {
var a = frame.environment.getVariable('Math');
assertEq(a instanceof Debugger.Object, true);
var b = gw.getOwnPropertyDescriptor('Math').value;
assertEq(a, b);
hits++;
};
g.eval("debugger;");
assertEq(hits, 1);

Просмотреть файл

@ -0,0 +1,17 @@
// getVariable that would trigger a getter does not crash or explode.
// It should throw WouldRunDebuggee, but that isn't implemented yet.
load(libdir + "asserts.js");
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var hits = 0;
dbg.onDebuggerStatement = function (frame) {
assertThrowsInstanceOf(function () {
frame.environment.getVariable("x");
}, Error);
hits++;
};
g.eval("Object.defineProperty(this, 'x', {get: function () { throw new Error('fail'); }});\n" +
"debugger;");
assertEq(hits, 1);

Просмотреть файл

@ -0,0 +1,9 @@
// Environment.prototype.setVariable can set global variables.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
dbg.onDebuggerStatement = function (frame) {
frame.environment.setVariable("x", 2);
};
g.eval("var x = 1; debugger;");
assertEq(g.x, 2);

Просмотреть файл

@ -0,0 +1,10 @@
// The argument to setVariable can be a Debugger.Object.
var g = newGlobal('new-compartment');
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
dbg.onDebuggerStatement = function (frame) {
frame.environment.setVariable("x", gw);
};
g.eval("var x = 1; debugger;");
assertEq(g.x, g);

Просмотреть файл

@ -0,0 +1,16 @@
// setVariable cannot create new global variables.
// (Other kinds of environment are tested in Environment-variables.js.)
load(libdir + "asserts.js");
var g = newGlobal('new-compartment');
var dbg = new Debugger(g);
var hits = 0;
dbg.onDebuggerStatement = function (frame) {
assertThrowsInstanceOf(function () { frame.environment.setVariable("x", 7); }, TypeError);
hits++;
};
g.eval("debugger");
assertEq("x" in g, false);
assertEq(hits, 1);

Просмотреть файл

@ -0,0 +1,10 @@
// setVariable can set variables and arguments in functions.
var g = newGlobal('new-compartment');
var dbg = new Debugger(g);
dbg.onDebuggerStatement = function (frame) {
frame.environment.setVariable("a", 100);
frame.environment.setVariable("b", 200);
};
g.eval("function f(a) { var b = a + 1; debugger; return a + b; }");
assertEq(g.f(1), 300);

Просмотреть файл

@ -0,0 +1,14 @@
// setVariable can change the types of variables and arguments in functions.
var g = newGlobal('new-compartment');
g.eval("function f(a) { var b = a + 1; debugger; return a + b; }");
for (var i = 0; i < 20; i++)
assertEq(g.f(i), 2 * i + 1);
var dbg = new Debugger(g);
dbg.onDebuggerStatement = function (frame) {
frame.environment.setVariable("a", "xyz");
frame.environment.setVariable("b", "zy");
};
for (var i = 0; i < 10; i++)
assertEq(g.f(i), "xyzzy");

Просмотреть файл

@ -0,0 +1,9 @@
// setVariable on an argument works as expected with non-strict 'arguments'.
var g = newGlobal('new-compartment');
g.eval("function f(a) { debugger; return arguments[0]; }");
var dbg = new Debugger(g);
dbg.onDebuggerStatement = function (frame) {
frame.environment.setVariable("a", 2);
};
assertEq(g.f(1), 2);

Просмотреть файл

@ -0,0 +1,15 @@
// setVariable works on let-bindings.
var g = newGlobal('new-compartment');
function test(code, val) {
g.eval("function f() { " + code + " }");
var dbg = new Debugger(g);
dbg.onDebuggerStatement = function (frame) {
frame.environment.setVariable("a", val);
};
assertEq(g.f(), val);
}
test("let a = 1; debugger; return a;", "xyzzy");
test("{ let a = 1; debugger; return a; }", "plugh");
test("let (a = 1) { debugger; return a; }", "wcgr");

Просмотреть файл

@ -0,0 +1,29 @@
// setVariable throws if no binding exists.
load(libdir + "asserts.js");
function test(code) {
var g = newGlobal('new-compartment');
var dbg = new Debugger(g);
var hits = 0;
dbg.onDebuggerStatement = function (frame) {
var env = frame.older.environment;
assertThrowsInstanceOf(function () { env.setVariable("y", 2); }, Error);
hits++;
};
g.eval("var y = 0; function d() { debugger; }");
assertEq(g.eval(code), 0);
assertEq(g.y, 0);
assertEq(hits, 1);
}
// local scope of non-heavyweight function
test("function f() { var x = 1; d(); return y; } f();");
// block scope
test("function h(x) { if (x) { let x = 1; d(); return y; } } h(3);");
// strict eval scope
test("'use strict'; eval('d(); y;');");

Просмотреть файл

@ -0,0 +1,32 @@
// setVariable works on non-innermost environments.
// (The debuggee code here is a bit convoluted to defeat optimizations that
// could make obj.b a null closure or obj.i a flat closure--that is, a function
// that gets a frozen copy of i instead of a reference to the runtime
// environment that contains it. setVariable does not currently detect this
// flat closure case.)
var g = newGlobal('new-compartment');
g.eval("function d() { debugger; }\n" +
"var i = 'FAIL';\n" +
"function a() {\n" +
" var obj = {b: function (i) { d(obj); return i; },\n" +
" i: function () { return i; }};\n" +
" var i = 'FAIL2';\n" +
" return obj;\n" +
"}\n");
var dbg = Debugger(g);
dbg.onDebuggerStatement = function (frame) {
var x = 0;
for (var env = frame.older.environment; env; env = env.parent) {
if (env.getVariable("i") !== undefined)
env.setVariable("i", x++);
}
};
var obj = g.a();
var r = obj.b('FAIL3');
assertEq(r, 0);
assertEq(obj.i(), 1);
assertEq(g.i, 2);

Просмотреть файл

@ -0,0 +1,16 @@
// setVariable cannot modify the binding for a FunctionExpression's name.
load(libdir + "asserts.js");
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var hits = 0;
dbg.onDebuggerStatement = function (frame) {
var env = frame.environment.find("f");
assertEq(env.getVariable("f"), frame.callee);
assertThrowsInstanceOf(function () { env.setVariable("f", 0) }, TypeError);
assertThrowsInstanceOf(function () { env.setVariable("f", frame.callee) }, TypeError);
hits++;
};
g.eval("(function f() { debugger; })();");
assertEq(hits, 1);

Просмотреть файл

@ -0,0 +1,21 @@
// setVariable can create a new property on a with block's bindings object, if
// it is shadowing an existing property on the prototype chain.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
dbg.onDebuggerStatement = function (frame) {
var env = frame.environment.find("x");
env.setVariable("x", 2);
};
g.eval("var obj1 = {x: 1}, obj2 = Object.create(obj1), z; with (obj2) { debugger; z = x; }");
assertEq(g.obj1.x, 1);
assertEq(g.obj2.x, 2);
assertEq(g.z, 2);
// The property created by setVariable is like the one created by ordinary
// assignment in a with-block.
var desc = Object.getOwnPropertyDescriptor(g.obj2, "x");
assertEq(desc.configurable, true);
assertEq(desc.enumerable, true);
assertEq(desc.writable, true);
assertEq(desc.value, 2);

Просмотреть файл

@ -0,0 +1,22 @@
// setVariable triggering a setter doesn't crash or explode.
// It should throw WouldRunDebuggee, but that isn't implemented yet.
function test(code) {
var g = newGlobal('new-compartment');
g.eval("function d() { debugger; }");
var dbg = Debugger(g);
var hits = 0;
dbg.onDebuggerStatement = function (frame) {
var env = frame.environment.find("x");
try {
env.setVariable("x", 0);
} catch (exc) {
}
hits++;
};
g.eval(code);
}
test("Object.defineProperty(this, 'x', {set: function (v) {}}); d();");
test("Object.defineProperty(Object.prototype, 'x', {set: function (v) {}}); d();");
test("with ({set x(v) {}}) eval(d());");

Просмотреть файл

@ -0,0 +1,92 @@
// Comprehensive test of get/setVariable on many kinds of environments and
// bindings.
load(libdir + "asserts.js");
var cases = [
// global bindings and bindings on the global prototype chain
"x = VAL; @@",
"var x = VAL; @@",
"Object.prototype.x = VAL; @@",
// let, catch, and comprehension bindings
"let x = VAL; @@",
"{ let x = VAL; @@ }",
"let (x = VAL) { @@ }",
"try { throw VAL; } catch (x) { @@ }",
"try { throw VAL; } catch (x) { @@ }",
"for (let x of [VAL]) { @@ }",
"for each (let x in [VAL]) { @@ }",
"switch (0) { default: let x = VAL; @@ }",
"[function () { @@ }() for (x of [VAL])];",
// "((function () { @@ })() for (x of [VAL])).next();", // bug 709367
// arguments
"function f(x) { @@ } f(VAL);",
"function f([w, x]) { @@ } f([0, VAL]);",
"function f({v: x}) { @@ } f({v: VAL});",
"function f([w, {v: x}]) { @@ } f([0, {v: VAL}]);",
// bindings in functions
"function f() { var x = VAL; @@ } f();",
"function f() { let x = VAL; @@ } f();",
"function f([x]) { let x = VAL; @@ } f(['fail']);",
"function f(x) { { let x = VAL; @@ } } f('fail');",
"function f() { function x() {} x = VAL; @@ } f();",
// dynamic bindings
"function f(s) { eval(s); @@ } f('var x = VAL');",
"function f(s) { let (x = 'fail') { eval(s); } x = VAL; @@ } f('var x;');",
"var x = VAL; function f(s) { eval('var x = 0;'); eval(s); @@ } f('delete x;');",
"function f(obj) { with (obj) { @@ } } f({x: VAL});",
"function f(obj) { with (obj) { @@ } } f(Object.create({x: VAL}));",
"function f(b) { if (b) { function x(){} } x = VAL; @@ } f(1);",
];
var nextval = 1000;
function test(code, debugStmts, followupStmts) {
var val = nextval++;
var hits = 0;
var g = newGlobal('new-compartment');
g.eval("function debugMe() { var x = 'wrong-x'; debugger; }");
g.capture = null;
var dbg = Debugger(g);
dbg.onDebuggerStatement = function (frame) {
if (frame.callee !== null && frame.callee.name == 'debugMe')
frame = frame.older;
var env = frame.environment.find("x");
assertEq(env.getVariable("x"), val)
assertEq(env.setVariable("x", 'ok'), undefined);
assertEq(env.getVariable("x"), 'ok');
// setVariable cannot create new variables.
assertThrowsInstanceOf(function () { env.setVariable("newVar", 0); }, TypeError);
hits++;
};
code = code.replace("@@", debugStmts);
if (followupStmts !== undefined)
code += " " + followupStmts;
code = code.replace(/VAL/g, uneval(val));
g.eval(code);
assertEq(hits, 1);
}
for (var s of cases) {
// Test triggering the debugger right in the scope in which x is bound.
test(s, "debugger; assertEq(x, 'ok');");
// Test calling a function that triggers the debugger.
test(s, "debugMe(); assertEq(x, 'ok');");
// Test triggering the debugger from a scope nested in x's scope.
test(s, "let (y = 'irrelevant') { (function (z) { let (zz = y) { debugger; }})(); } assertEq(x, 'ok');"),
// Test closing over the variable and triggering the debugger later, after
// leaving the variable's scope.
test(s, "capture = {dbg: function () { debugger; }, get x() { return x; }};",
"assertEq(capture.x, VAL); capture.dbg(); assertEq(capture.x, 'ok');");
}

Просмотреть файл

@ -377,3 +377,5 @@ MSG_DEF(JSMSG_EMPTY_CONSEQUENT, 290, 0, JSEXN_SYNTAXERR, "mistyped ; after
MSG_DEF(JSMSG_NOT_ITERABLE, 291, 1, JSEXN_TYPEERR, "{0} is not iterable") MSG_DEF(JSMSG_NOT_ITERABLE, 291, 1, JSEXN_TYPEERR, "{0} is not iterable")
MSG_DEF(JSMSG_QUERY_LINE_WITHOUT_URL, 292, 0, JSEXN_TYPEERR, "findScripts query object has 'line' property, but no 'url' property") MSG_DEF(JSMSG_QUERY_LINE_WITHOUT_URL, 292, 0, JSEXN_TYPEERR, "findScripts query object has 'line' property, but no 'url' property")
MSG_DEF(JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL, 293, 0, JSEXN_TYPEERR, "findScripts query object has 'innermost' property without both 'url' and 'line' properties") MSG_DEF(JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL, 293, 0, JSEXN_TYPEERR, "findScripts query object has 'innermost' property without both 'url' and 'line' properties")
MSG_DEF(JSMSG_DEBUG_VARIABLE_NOT_FOUND, 294, 0, JSEXN_TYPEERR, "variable not found in environment")

Просмотреть файл

@ -5261,7 +5261,7 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, unsigned defineHow,
prop = NULL; prop = NULL;
} }
} else { } else {
/* We should never add properties to lexical blocks. */ /* We should never add properties to lexical blocks. */
JS_ASSERT(!obj->isBlock()); JS_ASSERT(!obj->isBlock());
if (obj->isGlobal() && if (obj->isGlobal() &&

Просмотреть файл

@ -802,7 +802,7 @@ struct Shape : public js::gc::Cell
* care of making this work, but that suffices only because we require that * care of making this work, but that suffices only because we require that
* start points with the same shape have the same successor object in the * start points with the same shape have the same successor object in the
* search path --- a cache hit means the starting shapes were equal, which * search path --- a cache hit means the starting shapes were equal, which
* means the seach path tail (everything but the first object in the path) * means the search path tail (everything but the first object in the path)
* was shared, which in turn means the effects of a purge will be seen by * was shared, which in turn means the effects of a purge will be seen by
* all affected starting search points. * all affected starting search points.
* *

Просмотреть файл

@ -4305,7 +4305,7 @@ DebuggerEnv_getObject(JSContext *cx, unsigned argc, Value *vp)
static JSBool static JSBool
DebuggerEnv_names(JSContext *cx, unsigned argc, Value *vp) DebuggerEnv_names(JSContext *cx, unsigned argc, Value *vp)
{ {
THIS_DEBUGENV_OWNER(cx, argc, vp, "get type", args, envobj, env, dbg); THIS_DEBUGENV_OWNER(cx, argc, vp, "names", args, envobj, env, dbg);
AutoIdVector keys(cx); AutoIdVector keys(cx);
{ {
@ -4338,7 +4338,7 @@ static JSBool
DebuggerEnv_find(JSContext *cx, unsigned argc, Value *vp) DebuggerEnv_find(JSContext *cx, unsigned argc, Value *vp)
{ {
REQUIRE_ARGC("Debugger.Environment.find", 1); REQUIRE_ARGC("Debugger.Environment.find", 1);
THIS_DEBUGENV_OWNER(cx, argc, vp, "get type", args, envobj, env, dbg); THIS_DEBUGENV_OWNER(cx, argc, vp, "find", args, envobj, env, dbg);
jsid id; jsid id;
if (!ValueToIdentifier(cx, args[0], &id)) if (!ValueToIdentifier(cx, args[0], &id))
@ -4364,6 +4364,74 @@ DebuggerEnv_find(JSContext *cx, unsigned argc, Value *vp)
return dbg->wrapEnvironment(cx, env, &args.rval()); return dbg->wrapEnvironment(cx, env, &args.rval());
} }
static JSBool
DebuggerEnv_getVariable(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Environment.getVariable", 1);
THIS_DEBUGENV_OWNER(cx, argc, vp, "getVariable", args, envobj, env, dbg);
jsid id;
if (!ValueToIdentifier(cx, args[0], &id))
return false;
Value v;
{
AutoCompartment ac(cx, env);
if (!ac.enter() || !cx->compartment->wrapId(cx, &id))
return false;
/* This can trigger getters. */
ErrorCopier ec(ac, dbg->toJSObject());
if (!env->getGeneric(cx, id, &v))
return false;
}
if (!dbg->wrapDebuggeeValue(cx, &v))
return false;
args.rval() = v;
return true;
}
static JSBool
DebuggerEnv_setVariable(JSContext *cx, unsigned argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Environment.setVariable", 2);
THIS_DEBUGENV_OWNER(cx, argc, vp, "setVariable", args, envobj, env, dbg);
jsid id;
if (!ValueToIdentifier(cx, args[0], &id))
return false;
Value v = args[1];
if (!dbg->unwrapDebuggeeValue(cx, &v))
return false;
{
AutoCompartment ac(cx, env);
if (!ac.enter() || !cx->compartment->wrapId(cx, &id) || !cx->compartment->wrap(cx, &v))
return false;
/* This can trigger setters. */
ErrorCopier ec(ac, dbg->toJSObject());
/* Make sure the environment actually has the specified binding. */
bool has;
if (!env->hasProperty(cx, id, &has))
return false;
if (!has) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_VARIABLE_NOT_FOUND);
return false;
}
/* Just set the property. */
if (!env->setGeneric(cx, id, &v, true))
return false;
}
args.rval().setUndefined();
return true;
}
static JSPropertySpec DebuggerEnv_properties[] = { static JSPropertySpec DebuggerEnv_properties[] = {
JS_PSG("type", DebuggerEnv_getType, 0), JS_PSG("type", DebuggerEnv_getType, 0),
JS_PSG("object", DebuggerEnv_getObject, 0), JS_PSG("object", DebuggerEnv_getObject, 0),
@ -4374,6 +4442,8 @@ static JSPropertySpec DebuggerEnv_properties[] = {
static JSFunctionSpec DebuggerEnv_methods[] = { static JSFunctionSpec DebuggerEnv_methods[] = {
JS_FN("names", DebuggerEnv_names, 0, 0), JS_FN("names", DebuggerEnv_names, 0, 0),
JS_FN("find", DebuggerEnv_find, 1, 0), JS_FN("find", DebuggerEnv_find, 1, 0),
JS_FN("getVariable", DebuggerEnv_getVariable, 1, 0),
JS_FN("setVariable", DebuggerEnv_setVariable, 2, 0),
JS_FS_END JS_FS_END
}; };