A first cut at Debugger.Environment. Bug 690558, r=jimb.

--HG--
extra : rebase_source : 3cafc4e635f11b3219285fdf6fad952c15765f96
This commit is contained in:
Jason Orendorff 2011-12-08 14:54:26 -06:00
Родитель c510997b92
Коммит 1257a0203a
31 изменённых файлов: 975 добавлений и 40 удалений

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

@ -0,0 +1,23 @@
// A live Environment can observe the new variables introduced by ES5 non-strict direct eval.
var g = newGlobal('new-compartment');
g.eval("var x = 'global'; function f(s) { h(); eval(s); h(); }");
g.eval("function h() { debugger; }");
var dbg = Debugger(g);
var env = undefined;
var hits = 0;
dbg.onDebuggerStatement = function (hframe) {
if (env === undefined) {
// First debugger statement.
env = hframe.older.environment;
assertEq(env.find("x") !== env, true);
assertEq(env.names().indexOf("x"), -1);
} else {
// Second debugger statement, post-eval.
assertEq(env.find("x"), env);
assertEq(env.names().indexOf("x") >= 0, true);
}
hits++;
};
g.f("var x = 'local';");
assertEq(hits, 2);

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

@ -0,0 +1,20 @@
// The last Environment on the environment chain always has .type == "object" and .object === the global object.
var g = newGlobal('new-compartment');
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
g.eval("function h() { debugger; }");
var hits = 0;
dbg.onDebuggerStatement = function (hframe) {
var env = hframe.older.environment;
while (env.parent)
env = env.parent;
assertEq(env.type, "object");
assertEq(env.object, gw);
hits++;
};
g.eval("h();");
g.eval("(function () { h(); return []; })();");
g.eval("with (Math) { h(-2 * PI); }");
assertEq(hits, 3);

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

@ -0,0 +1,19 @@
// find sees that vars are hoisted out of with statements.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var hits = 0;
dbg.onDebuggerStatement = function (frame) {
assertEq(frame.environment.find("x").type, "object");
hits++;
};
assertEq(g.eval("(function () {\n" +
" function g() { x = 1; }\n" +
" with ({x: 2}) {\n" +
" var x;\n" +
" debugger;\n" +
" return x;\n" +
" }\n" +
"})();"), 2);
assertEq(hits, 1);

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

@ -0,0 +1,18 @@
// env.find() finds nonenumerable names in the global environment.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var hits = 0;
g.h = function () {
var env = dbg.getNewestFrame().environment;
var last = env;
while (last.parent)
last = last.parent;
assertEq(env.find("Array"), last);
hits++;
};
g.eval("h();");
g.eval("(function () { let (x = 1, y = 2) h(); })();");
assertEq(hits, 2);

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

@ -0,0 +1,20 @@
// env.find() finds noneumerable properties in with statements.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var hits = 0;
g.h = function () {
var frame = dbg.getNewestFrame();
var target = frame.eval("obj").return;
var env = frame.environment.find("PI");
assertEq(env.object, target);
hits++;
};
g.obj = g.Math;
g.eval("with (obj) h();");
g.eval("with (Math) { let x = 12; h(); }");
g.eval("obj = {};\n" +
"Object.defineProperty(obj, 'PI', {enumerable: false, value: 'Marlowe'});\n" +
"with (obj) h();\n");
assertEq(hits, 3);

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

@ -0,0 +1,21 @@
// env.find throws a TypeError if the argument is not an identifier.
load(libdir + "asserts.js");
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var hits = 0;
g.h = function () {
var env = dbg.getNewestFrame().environment;
assertThrowsInstanceOf(function () { env.find(); }, TypeError);
assertThrowsInstanceOf(function () { env.find(""); }, TypeError);
assertThrowsInstanceOf(function () { env.find(" "); }, TypeError);
assertThrowsInstanceOf(function () { env.find(0); }, TypeError);
assertThrowsInstanceOf(function () { env.find("0"); }, TypeError);
assertThrowsInstanceOf(function () { env.find("0xc"); }, TypeError);
assertThrowsInstanceOf(function () { env.find("Anna Karenina"); }, TypeError);
hits++;
};
g.eval("h();");
g.eval("with ([1]) h();");
assertEq(hits, 2);

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

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

@ -0,0 +1,49 @@
// Environment.prototype.find finds bindings that are function arguments, 'let'
// bindings, or FunctionExpression names.
var g = newGlobal('new-compartment');
g.eval("function h() { debugger; }");
var dbg = new Debugger(g);
function test1(code) {
var hits = 0;
dbg.onDebuggerStatement = function (frame) {
var env = frame.older.environment.find('X');
assertEq(env.names().indexOf('X') !== -1, true);
assertEq(env.type, 'declarative');
assertEq(env.parent !== null, true);
hits++;
};
g.eval(code);
assertEq(hits, 1);
}
var manyNames = '';
for (var i = 0; i < 4096; i++)
manyNames += 'x' + i + ', ';
manyNames += 'X';
function test2(code) {
print(code + " : one");
test1(code.replace('@@', 'X'));
print(code + " : many");
test1(code.replace('@@', manyNames));
}
test2('function f(@@) { h(); } f(1);');
test2('function f(@@) { h(); } f();');
test2('function f(@@) { return function g() { h(X); }; } f(1)();');
test2('function f(@@) { return function g() { h(X); }; } f()();');
test2(' { let @@ = 0; h(); }');
test2('function f(a, b, c) { let @@ = 0; h(); } f(1, 2, 3);');
test2(' { let @@ = 0; { let y = 0; h(); } }');
test2('function f() { let @@ = 0; { let y = 0; h(); } } f();');
test2(' { for (let @@ = 0; X < 1; X++) h(); }');
test2('function f() { for (let @@ = 0; X < 1; X++) h(); } f();');
test2(' { (let (@@ = 0) let (y = 2, z = 3) h()); }');
test2('function f() { return (let (@@ = 0) let (y = 2, z = 3) h()); } f();');
test1('(function X() { h(); })();');
test1('(function X(a, b, c) { h(); })(1, 2, 3);');

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

@ -0,0 +1,19 @@
// An Environment keeps its referent alive.
var g = newGlobal('new-compartment');
g.eval("function f(x) { return 2 * x; }");
var dbg = Debugger(g);
var env;
dbg.onEnterFrame = function (frame) { env = frame.environment; };
assertEq(g.f(22), 44);
dbg.onEnterFrame = undefined;
assertEq(env.find("x"), env);
assertEq(env.names().join(","), "x");
gc();
g.gc(g);
gc(env);
assertEq(env.find("x"), env);
assertEq(env.names().join(","), "x");

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

@ -0,0 +1,28 @@
// A closure's .environment keeps the lexical environment alive even if the closure is destroyed.
var N = 4;
var g = newGlobal('new-compartment');
g.eval("function add(a) { return function (b) { return eval('a + b'); }; }");
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
var aw = gw.getOwnPropertyDescriptor("add").value;
// Create N closures and collect environments.
var arr = [];
for (var i = 0; i < N; i++)
arr[i] = aw.call(null, i).return.environment;
// Test that they work now.
function check() {
for (var i = 0; i < N; i++) {
assertEq(arr[i].find("b"), null);
assertEq(arr[i].find("a"), arr[i]);
}
}
check();
// Test that they work after gc.
gc();
gc({});
g.gc(g);
check();

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

@ -0,0 +1,40 @@
// The value of frame.environment is the same Environment object at different
// times within a single visit to a scope.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
g.eval("function h() { debugger; }");
var hits, env;
dbg.onDebuggerStatement = function (hframe) {
var frame = hframe.older;
var e = frame.environment;
// frame.environment is at least cached from one moment to the next.
assertEq(e, frame.environment);
// frame.environment is cached from statement to statement within a call frame.
if (env === undefined)
env = e;
else
assertEq(e, env);
hits++;
};
hits = 0;
env = undefined;
g.eval("function f() { (function () { var i = 0; h(); var j = 2; h(); })(); }");
g.f();
assertEq(hits, 2);
hits = 0;
env = undefined;
g.eval("function f2() { { let i = 0; h(); let j = 2; h(); } }");
g.f2();
assertEq(hits, 2);
hits = 0;
env = undefined;
g.eval("function f3() { { let i; for (i = 0; i < 2; i++) h(); } }");
g.f3();
assertEq(hits, 2);

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

@ -0,0 +1,29 @@
// frame.environment is different for different activations of a scope.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
g.eval("function h() { debugger; }");
var arr;
dbg.onDebuggerStatement = function (hframe) {
var e = hframe.older.environment;
assertEq(arr.indexOf(e), -1);
arr.push(e);
};
function test(code, expectedHits) {
arr = [];
g.eval(code);
assertEq(arr.length, expectedHits);
}
// two separate calls to a function
test("(function () { var f = function (a) { h(); return a; }; f(1); f(2); })();", 2);
// recursive calls to a function
test("(function f(n) { h(); return n < 2 ? 1 : n * f(n - 1); })(3);", 3);
// separate visits to a block in the same call frame
test("(function () { for (var i = 0; i < 3; i++) { let j = i * 4; h(); }})();", 3);
// two strict direct eval calls in the same function scope
test("(function () { 'use strict'; for (var i = 0; i < 3; i++) eval('h();'); })();", 3);

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

@ -0,0 +1,109 @@
// Two Environments nested in the same runtime scope share the correct tail of their parent chains.
// The compiler must be allowed to elide empty scopes and so forth, so this
// test does not check the number of unshared Environments. Instead, each test
// case identifies the expected innermost shared scope by the name of a
// variable in it.
var g = newGlobal('new-compartment');
g.eval("function h() { debugger; }");
var dbg = Debugger(g);
var hits, name, shared, unshared;
dbg.onDebuggerStatement = function (hframe) {
var frame = hframe.older;
// Find name in frame.environment.
var env, child = null;
for (env = frame.environment; env !== null; env = env.parent) {
if (env.names().indexOf(name) != -1)
break;
child = env;
}
assertEq(env !== null, true, "expected '" + name + "' to be in scope");
assertEq(env, frame.environment.find(name),
"env.find should find the same frame as the written out search");
if (hits === 0) {
// First hit.
shared = env;
unshared = child;
} else {
// Subsequent hit.
assertEq(env, shared, "the environment containing '" + name + "' should be shared");
assertEq(child === null || unshared === null || unshared !== child, true,
"environments nested within the one containing '" + name + "' should not be shared");
}
hits++;
};
function test(sharedName, expectedHits, code) {
hits = 0;
name = sharedName;
shared = unshared = undefined;
g.eval(code);
assertEq(hits, expectedHits);
}
// Basic test cases.
//
// (The stray "a = b" assignments in these tests are to inhibit the flat closure
// optimization, which Environments expose. There's nothing really wrong with
// the optimization or with the debugger exposing it, but that's not what we
// want to test here.)
test("q", 2, "var q = function (a) { h(); }; q(1); q(2);");
test("a", 2, "q = function (a) { (function (b) { h(); a = b; })(2); h(); }; q(1);");
test("a", 2, "q = function (a) { h(); return function (b) { h(); a = b; }; }; q(1)(2);");
test("n", 3, "q = function (n) { for (var i = 0; i < n; i++) { let (j = i) { h(); } } }; q(3);");
// Don't crash in E4X filter scopes.
test("x", 2, "q = function () { var x = <><y/><z/></>.(function (e) { h(); }(this)); }; q();");
// A function with long dynamic and static chains.
var N = 80;
var code = "function f" + N + "(a" + N + ") {\neval('a0 + a1'); h();\n}\n";
for (var i = N; --i >= 0;) {
var call = "f" + (i + 1) + "(a" + i + " - 1);\n";
code = ("function f" + i + "(a" + i + ") {\n" +
code +
call +
"if (a" + i + " === 0) " + call +
"}\n");
}
g.eval(code);
test("a0", 2, "f0(0);");
test("a17", 2, "f0(17);");
test("a" + (N-2), 2, "f0(" + (N-2) + ");");
test("a" + (N-1), 2, "f0(" + (N-1) + ");");
// A function with a short dynamic chain and a long static chain.
N = 60;
function DeepStaticShallowDynamic(i, n) {
var code = "function f" + i + "(a" + i + ") {\n";
if (i >= n)
code += "eval('a1 + a2'); h();\n";
else
code += "return " + DeepStaticShallowDynamic(i+1, n) + ";\n";
code += "}";
return code;
}
g.eval(DeepStaticShallowDynamic(1, N));
function range(start, stop) {
for (var i = start; i < stop; i++)
yield i;
}
function DSSDsplit(s) {
return ("var mid = f1" + ["(" + i + ")" for (i in range(0, s))].join("") + ";\n" +
"mid" + ["(" + i + ")" for (i in range(s, N))].join("") + ";\n" +
"mid" + ["(" + i + ")" for (i in range(s, N))].join("") + ";\n");
}
test("a1", 2, DSSDsplit(1));
test("a17", 2, DSSDsplit(17));
test("a" + (N-2), 2, DSSDsplit(N-2));
test("a" + (N-1), 2, DSSDsplit(N-1));

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

@ -0,0 +1,19 @@
// Observably different visits to the same with-statement produce distinct Environments.
var g = newGlobal('new-compartment');
g.eval("function f(a, obj) { with (obj) return function () { return a; }; }");
var dbg = Debugger(g);
var hits = 0;
dbg.onDebuggerStatement = function (frame) {
// Even though the two visits to the with-statement have the same target
// object, Math, the environments are observably different.
var f1 = frame.eval("f(1, Math);").return;
var f2 = frame.eval("f(2, Math);").return;
assertEq(f1.environment !== f2.environment, true);
assertEq(f1.object, f2.object);
assertEq(f1.call().return, 1);
assertEq(f2.call().return, 2);
hits++;
};
g.eval("debugger;");
assertEq(hits, 1);

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

@ -0,0 +1,17 @@
// env.names() lists nonenumerable names in with-statement environments.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var hits = 0;
g.h = function () {
var env = dbg.getNewestFrame().environment;
var names = env.names();
assertEq(names.indexOf("a") !== -1, true);
assertEq(names.indexOf("b") !== -1, true);
assertEq(names.indexOf("isPrototypeOf") !== -1, true);
hits++;
};
g.eval("var obj = {a: 1};\n" +
"Object.defineProperty(obj, 'b', {value: 2});\n" +
"with (obj) h();");
assertEq(hits, 1);

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

@ -0,0 +1,15 @@
// env.names() on object environments ignores property names that are not identifiers.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var names;
g.h = function () {
names = dbg.getNewestFrame().environment.names();
};
g.eval("var obj = {a: 1};\n" +
"with ({a: 1, '0xcafe': 2, ' ': 3, '': 4, '0': 5}) h();");
assertEq(names.indexOf("a") !== -1, true);
assertEq(names.indexOf("0xcafe"), -1);
assertEq(names.indexOf(" "), -1);
assertEq(names.indexOf(""), -1);
assertEq(names.indexOf("0"), -1);

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

@ -0,0 +1,18 @@
// The objects on the environment chain are all Debugger.Environment objects.
// The environment chain ends in null.
var g = newGlobal('new-compartment')
g.eval("function f(a) { return function (b) { return function (c) { h(); return a + b + c; }; }; }");
var dbg = Debugger(g);
var hits = 0;
g.h = function () {
var n = 0;
for (var env = dbg.getNewestFrame().environment; env !== null; env = env.parent) {
n++;
assertEq(env instanceof Debugger.Environment, true);
}
assertEq(n >= 4, true);
hits++;
};
assertEq(g.f(5)(7)(9), 21);
assertEq(hits, 1);

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

@ -0,0 +1,38 @@
// env.type is 'object' in global environments and with-blocks, and 'declarative' otherwise.
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
function test(code, expected) {
var actual = '';
g.h = function () { actual += dbg.getNewestFrame().environment.type; }
g.eval(code);
assertEq(actual, expected);
}
test("h();", 'object');
test("(function (s) { eval(s); })('var v = h();')", 'declarative');
test("(function (s) { h(); })();", 'declarative');
test("{let x = 1, y = 2; h();}", 'declarative');
test("with({x: 1, y: 2}) h();", 'object');
test("(function (s) { with ({x: 1, y: 2}) h(); })();", 'object');
test("let (x = 1) { h(); }", 'declarative');
test("(let (x = 1) h());", 'declarative');
test("for (let x = 0; x < 1; x++) h();", 'declarative');
test("for (let x in h()) ;", 'object');
test("for (let x in {a:1}) h();", 'declarative');
test("try { throw new Error; } catch (x) { h(x) }", 'declarative');
test("'use strict'; eval('var z = 1; h();');", 'declarative');
test("for (var x in [h(m) for (m in [1])]) ;", 'declarative');
test("for (var x in (h(m) for (m in [1]))) ;", 'declarative');
// Since a generator-expression is effectively a function, the innermost scope
// is a function scope, and thus declarative. Thanks to an odd design decision,
// m is already in scope at the point of the call to h(). The answer here is
// not all that important, but we shouldn't crash.
test("for (var x in (0 for (m in h()))) ;", 'declarative');
dbg.onDebuggerStatement = function (frame) {
assertEq(frame.eval("h(), 2 + 2;").return, 4);
}
test("debugger;", 'object');
test("(function f() { debugger; })();", 'declarative');

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

@ -0,0 +1,13 @@
// frame.environment is a Debugger.Environment object
var g = newGlobal('new-compartment')
var dbg = Debugger(g);
g.h = function () {
assertEq(dbg.getNewestFrame().environment instanceof Debugger.Environment, true);
};
g.eval("h()");
g.evaluate("h()");
g.eval("eval('h()')");
g.eval("function f() { h(); }");
g.f();

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

@ -0,0 +1,12 @@
// dbg.getNewestFrame().environment works.
var g = newGlobal('new-compartment');
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
g.h = function () {
var env = dbg.getNewestFrame().environment;
assertEq(env instanceof Debugger.Environment, true);
assertEq(env.object, gw);
assertEq(env.parent, null);
};
g.eval("h()");

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

@ -0,0 +1,11 @@
// If !frame.live, frame.environment throws.
load(libdir + "asserts.js");
var g = newGlobal('new-compartment');
var dbg = Debugger(g);
var frame;
g.h = function () { frame = dbg.getNewestFrame(); };
g.eval("h();");
assertEq(frame.live, false);
assertThrowsInstanceOf(function () { frame.environment; }, Error);

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

@ -0,0 +1,12 @@
// frame.environment can be called from the onEnterFrame hook.
var g = newGlobal('new-compartment');
g.eval("function f(x) { return 2 * x; }");
var dbg = Debugger(g);
var hits = 0;
dbg.onEnterFrame = function (frame) {
assertEq(frame.environment.names().join(","), "x");
hits++;
};
assertEq(g.f(22), 44);
assertEq(hits, 1);

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

@ -1,4 +1,5 @@
// Direct eval code under evalWithbindings sees both the bindings and the enclosing scope.
// Direct eval code under evalWithBindings sees both the bindings and the enclosing scope.
var g = newGlobal('new-compartment');
var dbg = new Debugger(g);
var hits = 0;

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

@ -0,0 +1,17 @@
// obj.environment is undefined when the referent is not a JS function.
var g = newGlobal('new-compartment')
var dbg = new Debugger;
var gw = dbg.addDebuggee(g);
assertEq(gw.environment, undefined);
g.eval("var r = /x/;");
var rw = gw.getOwnPropertyDescriptor("r").value;
assertEq(rw.class, "RegExp");
assertEq(rw.environment, undefined);
// Native function.
var fw = gw.getOwnPropertyDescriptor("parseInt").value;
assertEq(fw.class, "Function");
assertEq(fw.environment, undefined);

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

@ -0,0 +1,20 @@
// The .environment of a function Debugger.Object is an Environment object.
var g = newGlobal('new-compartment')
var dbg = Debugger(g);
var hits = 0;
g.h = function () {
var frame = dbg.getNewestFrame();
var fn = frame.eval("j").return;
assertEq(fn.environment instanceof Debugger.Environment, true);
var closure = frame.eval("f").return;
assertEq(closure.environment instanceof Debugger.Environment, true);
hits++;
};
g.eval("function j(a) {\n" +
" var f = function () { return a; };\n" +
" h();\n" +
" return f;\n" +
"}\n" +
"j(0);\n");
assertEq(hits, 1);

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

@ -372,3 +372,4 @@ MSG_DEF(JSMSG_DEBUG_COMPARTMENT_MISMATCH, 285, 2, JSEXN_TYPEERR, "{0}: descripto
MSG_DEF(JSMSG_DEBUG_NOT_SCRIPT_FRAME, 286, 0, JSEXN_ERR, "stack frame is not running JavaScript code")
MSG_DEF(JSMSG_CANT_WATCH_PROP, 287, 0, JSEXN_TYPEERR, "properties whose names are objects can't be watched")
MSG_DEF(JSMSG_CSP_BLOCKED_EVAL, 288, 0, JSEXN_ERR, "call to eval() blocked by CSP")
MSG_DEF(JSMSG_DEBUG_NO_SCOPE_OBJECT, 289, 0, JSEXN_TYPEERR, "declarative Environments don't have binding objects")

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

@ -227,7 +227,8 @@ class CompartmentChecker
}
void check(StackFrame *fp) {
check(&fp->scopeChain());
if (fp)
check(&fp->scopeChain());
}
};

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

@ -757,16 +757,16 @@ JS_EvaluateUCInStackFrame(JSContext *cx, JSStackFrame *fpArg,
if (!CheckDebugMode(cx))
return false;
JSObject *scobj = JS_GetFrameScopeChain(cx, fpArg);
if (!scobj)
Env *env = JS_GetFrameScopeChain(cx, fpArg);
if (!env)
return false;
js::AutoCompartment ac(cx, scobj);
js::AutoCompartment ac(cx, env);
if (!ac.enter())
return false;
StackFrame *fp = Valueify(fpArg);
return EvaluateInScope(cx, scobj, fp, chars, length, filename, lineno, rval);
return EvaluateInEnv(cx, env, fp, chars, length, filename, lineno, rval);
}
JS_PUBLIC_API(JSBool)

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

@ -243,6 +243,14 @@ typedef HashMap<JSAtom *,
RuntimeAllocPolicy>
RegExpPrivateCache;
/*
* Env is the type of what ES5 calls "lexical environments" (runtime
* activations of lexical scopes). This is currently just JSObject, and is
* implemented by Call, Block, With, and DeclEnv objects, among others--but
* environments and objects are really two different concepts.
*/
typedef JSObject Env;
typedef JSNative Native;
typedef JSPropertyOp PropertyOp;
typedef JSStrictPropertyOp StrictPropertyOp;

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

@ -78,6 +78,13 @@ enum {
JSSLOT_DEBUGARGUMENTS_COUNT
};
extern Class DebuggerEnv_class;
enum {
JSSLOT_DEBUGENV_OWNER,
JSSLOT_DEBUGENV_COUNT
};
extern Class DebuggerObject_class;
enum {
@ -121,6 +128,21 @@ ReportObjectRequired(JSContext *cx)
return false;
}
bool
ValueToIdentifier(JSContext *cx, const Value &v, jsid *idp)
{
jsid id;
if (!ValueToId(cx, v, &id))
return false;
if (!JSID_IS_ATOM(id) || !IsIdentifier(JSID_TO_ATOM(id))) {
js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
JSDVG_SEARCH_STACK, v, NULL, "not an identifier", NULL);
return false;
}
*idp = id;
return true;
}
/*** Breakpoints *********************************************************************************/
@ -297,7 +319,7 @@ Breakpoint::nextInSite()
Debugger::Debugger(JSContext *cx, JSObject *dbg)
: object(dbg), uncaughtExceptionHook(NULL), enabled(true),
frames(cx), objects(cx), scripts(cx)
frames(cx), scripts(cx), objects(cx), environments(cx)
{
assertSameCompartment(cx, dbg);
@ -319,24 +341,27 @@ Debugger::~Debugger()
bool
Debugger::init(JSContext *cx)
{
bool ok = frames.init() &&
bool ok = debuggees.init() &&
frames.init() &&
scripts.init() &&
objects.init() &&
debuggees.init() &&
scripts.init();
environments.init();
if (!ok)
js_ReportOutOfMemory(cx);
return ok;
}
JS_STATIC_ASSERT(uintN(JSSLOT_DEBUGFRAME_OWNER) == uintN(JSSLOT_DEBUGOBJECT_OWNER));
JS_STATIC_ASSERT(uintN(JSSLOT_DEBUGFRAME_OWNER) == uintN(JSSLOT_DEBUGSCRIPT_OWNER));
JS_STATIC_ASSERT(uintN(JSSLOT_DEBUGFRAME_OWNER) == uintN(JSSLOT_DEBUGOBJECT_OWNER));
JS_STATIC_ASSERT(uintN(JSSLOT_DEBUGFRAME_OWNER) == uintN(JSSLOT_DEBUGENV_OWNER));
Debugger *
Debugger::fromChildJSObject(JSObject *obj)
{
JS_ASSERT(obj->getClass() == &DebuggerFrame_class ||
obj->getClass() == &DebuggerScript_class ||
obj->getClass() == &DebuggerObject_class ||
obj->getClass() == &DebuggerScript_class);
obj->getClass() == &DebuggerEnv_class);
JSObject *dbgobj = &obj->getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER).toObject();
return fromJSObject(dbgobj);
}
@ -469,6 +494,35 @@ Debugger::slowPathOnLeaveFrame(JSContext *cx)
}
}
bool
Debugger::wrapEnvironment(JSContext *cx, Env *env, Value *rval)
{
if (!env) {
rval->setNull();
return true;
}
JSObject *envobj;
ObjectWeakMap::AddPtr p = environments.lookupForAdd(env);
if (p) {
envobj = p->value;
} else {
/* Create a new Debugger.Environment for env. */
JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_ENV_PROTO).toObject();
envobj = NewObjectWithGivenProto(cx, &DebuggerEnv_class, proto, NULL);
if (!envobj)
return false;
envobj->setPrivate(env);
envobj->setReservedSlot(JSSLOT_DEBUGENV_OWNER, ObjectValue(*object));
if (!environments.relookupOrAdd(p, env, envobj)) {
js_ReportOutOfMemory(cx);
return false;
}
}
rval->setObject(*envobj);
return true;
}
bool
Debugger::wrapDebuggeeValue(JSContext *cx, Value *vp)
{
@ -1013,6 +1067,13 @@ Debugger::markKeysInCompartment(JSTracer *tracer)
gc::MarkObject(tracer, key, "cross-compartment WeakMap key");
}
const ObjectMap &envStorage = environments;
for (ObjectMap::Range r = envStorage.all(); !r.empty(); r.popFront()) {
const HeapPtrObject &key = r.front().key;
if (key->compartment() == comp && IsAboutToBeFinalized(tracer->context, key))
js::gc::MarkObject(tracer, key, "cross-compartment WeakMap key");
}
typedef HashMap<HeapPtrScript, HeapPtrObject, DefaultHasher<HeapPtrScript>, RuntimeAllocPolicy>
ScriptMap;
const ScriptMap &scriptStorage = scripts;
@ -1178,11 +1239,14 @@ Debugger::trace(JSTracer *trc)
MarkObject(trc, frameobj, "live Debugger.Frame");
}
/* Trace the weak map from JSScript instances to Debugger.Script objects. */
scripts.trace(trc);
/* Trace the referent -> Debugger.Object weak map. */
objects.trace(trc);
/* Trace the weak map from JSScript instances to Debugger.Script objects. */
scripts.trace(trc);
/* Trace the referent -> Debugger.Environment weak map. */
environments.trace(trc);
}
void
@ -2402,6 +2466,10 @@ StackContains(JSContext *cx, StackFrame *fp)
StackFrame *fp = (StackFrame *) thisobj->getPrivate(); \
JS_ASSERT(StackContains(cx, fp))
#define THIS_FRAME_OWNER(cx, argc, vp, fnname, args, thisobj, fp, dbg) \
THIS_FRAME(cx, argc, vp, fnname, args, thisobj, fp); \
Debugger *dbg = Debugger::fromChildJSObject(thisobj)
static JSBool
DebuggerFrame_getType(JSContext *cx, uintN argc, Value *vp)
{
@ -2419,6 +2487,33 @@ DebuggerFrame_getType(JSContext *cx, uintN argc, Value *vp)
return true;
}
static Env *
Frame_GetEnv(JSContext *cx, StackFrame *fp)
{
assertSameCompartment(cx, fp);
if (fp->isNonEvalFunctionFrame() && !fp->hasCallObj() && !CreateFunCallObject(cx, fp))
return NULL;
return GetScopeChain(cx, fp);
}
static JSBool
DebuggerFrame_getEnvironment(JSContext *cx, uintN argc, Value *vp)
{
THIS_FRAME_OWNER(cx, argc, vp, "get environment", args, thisobj, fp, dbg);
Env *env;
{
AutoCompartment ac(cx, &fp->scopeChain());
if (!ac.enter())
return false;
env = Frame_GetEnv(cx, fp);
if (!env)
return false;
}
return dbg->wrapEnvironment(cx, env, &args.rval());
}
static JSBool
DebuggerFrame_getCallee(JSContext *cx, uintN argc, Value *vp)
{
@ -2690,14 +2785,16 @@ DebuggerFrame_setOnStep(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)
EvaluateInEnv(JSContext *cx, Env *env, StackFrame *fp, const jschar *chars,
uintN length, const char *filename, uintN lineno, Value *rval)
{
assertSameCompartment(cx, scobj, fp);
assertSameCompartment(cx, env, fp);
/* Execute assumes an already-computed 'this" value. */
if (!ComputeThis(cx, fp))
return false;
if (fp) {
/* Execute assumes an already-computed 'this" value. */
if (!ComputeThis(cx, fp))
return false;
}
/*
* NB: This function breaks the assumption that the compiler can see all
@ -2705,7 +2802,7 @@ EvaluateInScope(JSContext *cx, JSObject *scobj, StackFrame *fp, const jschar *ch
* we use a static level that will cause us not to attempt to optimize
* variable references made by this frame.
*/
JSScript *script = frontend::CompileScript(cx, scobj, fp,
JSScript *script = frontend::CompileScript(cx, env, fp,
fp->scopeChain().principals(cx),
TCF_COMPILE_N_GO | TCF_NEED_SCRIPT_GLOBAL,
chars, length, filename, lineno,
@ -2715,7 +2812,7 @@ EvaluateInScope(JSContext *cx, JSObject *scobj, StackFrame *fp, const jschar *ch
if (!script)
return false;
return ExecuteKernel(cx, script, *scobj, fp->thisValue(), EXECUTE_DEBUG, fp, rval);
return ExecuteKernel(cx, script, *env, fp->thisValue(), EXECUTE_DEBUG, fp, rval);
}
}
@ -2772,19 +2869,19 @@ DebuggerFrameEval(JSContext *cx, uintN argc, Value *vp, EvalBindingsMode mode)
if (!ac.enter())
return false;
JSObject *scobj = JS_GetFrameScopeChain(cx, Jsvalify(fp));
if (!scobj)
Env *env = JS_GetFrameScopeChain(cx, Jsvalify(fp));
if (!env)
return false;
/* If evalWithBindings, create the inner scope object. */
/* If evalWithBindings, create the inner environment. */
if (mode == WithBindings) {
/* TODO - Should probably create a With object here. */
scobj = NewObjectWithGivenProto(cx, &ObjectClass, NULL, scobj);
if (!scobj)
/* TODO - This should probably be a Call object, like ES5 strict eval. */
env = NewObjectWithGivenProto(cx, &ObjectClass, NULL, env);
if (!env)
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))
!DefineNativeProperty(cx, env, keys[i], values[i], NULL, NULL, 0, 0, 0))
{
return false;
}
@ -2794,8 +2891,8 @@ DebuggerFrameEval(JSContext *cx, uintN argc, Value *vp, EvalBindingsMode mode)
/* Run the code and produce the completion value. */
Value rval;
JS::Anchor<JSString *> anchor(linearStr);
bool ok = EvaluateInScope(cx, scobj, fp, linearStr->chars(), linearStr->length(),
"debugger eval code", 1, &rval);
bool ok = EvaluateInEnv(cx, env, fp, linearStr->chars(), linearStr->length(),
"debugger eval code", 1, &rval);
return dbg->newCompletionValue(ac, ok, rval, vp);
}
@ -2822,6 +2919,7 @@ static JSPropertySpec DebuggerFrame_properties[] = {
JS_PSG("arguments", DebuggerFrame_getArguments, 0),
JS_PSG("callee", DebuggerFrame_getCallee, 0),
JS_PSG("constructing", DebuggerFrame_getConstructing, 0),
JS_PSG("environment", DebuggerFrame_getEnvironment, 0),
JS_PSG("generator", DebuggerFrame_getGenerator, 0),
JS_PSG("live", DebuggerFrame_getLive, 0),
JS_PSG("offset", DebuggerFrame_getOffset, 0),
@ -3014,14 +3112,16 @@ DebuggerObject_getScript(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get script", args, dbg, obj);
args.rval().setUndefined();
if (!obj->isFunction())
if (!obj->isFunction()) {
args.rval().setUndefined();
return true;
}
JSFunction *fun = obj->toFunction();
if (!fun->isInterpreted())
if (!fun->isInterpreted()) {
args.rval().setUndefined();
return true;
}
JSObject *scriptObject = dbg->wrapScript(cx, fun->script());
if (!scriptObject)
@ -3031,6 +3131,21 @@ DebuggerObject_getScript(JSContext *cx, uintN argc, Value *vp)
return true;
}
static JSBool
DebuggerObject_getEnvironment(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGOBJECT_OWNER_REFERENT(cx, argc, vp, "get environment", args, dbg, obj);
/* Don't bother switching compartments just to check obj's type and get its env. */
if (!obj->isFunction() || !obj->toFunction()->isInterpreted()) {
args.rval().setUndefined();
return true;
}
Env *env = obj->toFunction()->environment();
return dbg->wrapEnvironment(cx, env, &args.rval());
}
static JSBool
DebuggerObject_getOwnPropertyDescriptor(JSContext *cx, uintN argc, Value *vp)
{
@ -3446,6 +3561,7 @@ static JSPropertySpec DebuggerObject_properties[] = {
JS_PSG("name", DebuggerObject_getName, 0),
JS_PSG("parameterNames", DebuggerObject_getParameterNames, 0),
JS_PSG("script", DebuggerObject_getScript, 0),
JS_PSG("environment", DebuggerObject_getEnvironment, 0),
JS_PS_END
};
@ -3466,6 +3582,208 @@ static JSFunctionSpec DebuggerObject_methods[] = {
JS_FS_END
};
/*** Debugger.Environment ************************************************************************/
static void
DebuggerEnv_trace(JSTracer *trc, JSObject *obj)
{
if (!trc->runtime->gcCurrentCompartment) {
/*
* There is a barrier on private pointers, so the Unbarriered marking
* is okay.
*/
if (Env *referent = (JSObject *) obj->getPrivate())
MarkObjectUnbarriered(trc, referent, "Debugger.Environment referent");
}
}
Class DebuggerEnv_class = {
"Environment", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGENV_COUNT),
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, NULL,
NULL, /* reserved0 */
NULL, /* checkAccess */
NULL, /* call */
NULL, /* construct */
NULL, /* xdrObject */
NULL, /* hasInstance */
DebuggerEnv_trace
};
static JSObject *
DebuggerEnv_checkThis(JSContext *cx, const CallArgs &args, const char *fnname)
{
if (!args.thisv().isObject()) {
ReportObjectRequired(cx);
return NULL;
}
JSObject *thisobj = &args.thisv().toObject();
if (thisobj->getClass() != &DebuggerEnv_class) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
"Debugger.Environment", fnname, thisobj->getClass()->name);
return NULL;
}
/*
* Forbid Debugger.Environment.prototype, which is of class DebuggerEnv_class
* but isn't a real working Debugger.Environment. The prototype object is
* distinguished by having no referent.
*/
if (!thisobj->getPrivate()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
"Debugger.Environment", fnname, "prototype object");
return NULL;
}
return thisobj;
}
#define THIS_DEBUGENV(cx, argc, vp, fnname, args, envobj, env) \
CallArgs args = CallArgsFromVp(argc, vp); \
JSObject *envobj = DebuggerEnv_checkThis(cx, args, fnname); \
if (!envobj) \
return false; \
Env *env = static_cast<Env *>(envobj->getPrivate()); \
JS_ASSERT(env)
#define THIS_DEBUGENV_OWNER(cx, argc, vp, fnname, args, envobj, env, dbg) \
THIS_DEBUGENV(cx, argc, vp, fnname, args, envobj, env); \
Debugger *dbg = Debugger::fromChildJSObject(envobj)
static JSBool
DebuggerEnv_construct(JSContext *cx, uintN argc, Value *vp)
{
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_CONSTRUCTOR, "Debugger.Environment");
return false;
}
static JSBool
DebuggerEnv_getType(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGENV(cx, argc, vp, "get type", args, envobj, env);
/* Don't bother switching compartments just to check env's class. */
const char *s;
if (env->isCall() || env->isBlock() || env->isDeclEnv())
s = "declarative";
else
s = "object";
JSAtom *str = js_Atomize(cx, s, strlen(s), InternAtom, NormalEncoding);
if (!str)
return false;
args.rval().setString(str);
return true;
}
static JSBool
DebuggerEnv_getParent(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGENV_OWNER(cx, argc, vp, "get parent", args, envobj, env, dbg);
/* Don't bother switching compartments just to get env's parent. */
Env *parent = env->scopeChain();
return dbg->wrapEnvironment(cx, parent, &args.rval());
}
static JSBool
DebuggerEnv_getObject(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGENV_OWNER(cx, argc, vp, "get type", args, envobj, env, dbg);
/*
* Don't bother switching compartments just to check env's class and
* possibly get its proto.
*/
if (env->isCall() || env->isBlock() || env->isDeclEnv()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NO_SCOPE_OBJECT);
return false;
}
JSObject *obj = env->isWith() ? env->getProto() : env;
Value rval = ObjectValue(*obj);
if (!dbg->wrapDebuggeeValue(cx, &rval))
return false;
args.rval() = rval;
return true;
}
static JSBool
DebuggerEnv_names(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGENV_OWNER(cx, argc, vp, "get type", args, envobj, env, dbg);
AutoIdVector keys(cx);
{
AutoCompartment ac(cx, env);
if (!ac.enter())
return false;
ErrorCopier ec(ac, dbg->toJSObject());
if (!GetPropertyNames(cx, env, JSITER_HIDDEN, &keys))
return false;
}
JSObject *arr = NewDenseEmptyArray(cx);
if (!arr)
return false;
for (size_t i = 0, len = keys.length(); i < len; i++) {
jsid id = keys[i];
if (JSID_IS_ATOM(id) && IsIdentifier(JSID_TO_ATOM(id))) {
if (!cx->compartment->wrapId(cx, &id))
return false;
if (!js_NewbornArrayPush(cx, arr, StringValue(JSID_TO_STRING(id))))
return false;
}
}
args.rval().setObject(*arr);
return true;
}
static JSBool
DebuggerEnv_find(JSContext *cx, uintN argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Environment.find", 1);
THIS_DEBUGENV_OWNER(cx, argc, vp, "get type", args, envobj, env, dbg);
jsid id;
if (!ValueToIdentifier(cx, args[0], &id))
return false;
{
AutoCompartment ac(cx, env);
if (!ac.enter() || !cx->compartment->wrapId(cx, &id))
return false;
/* This can trigger resolve hooks. */
ErrorCopier ec(ac, dbg->toJSObject());
JSProperty *prop = NULL;
JSObject *pobj;
for (; env && !prop; env = env->scopeChain()) {
if (!env->lookupGeneric(cx, id, &pobj, &prop))
return false;
if (prop)
break;
}
}
return dbg->wrapEnvironment(cx, env, &args.rval());
}
static JSPropertySpec DebuggerEnv_properties[] = {
JS_PSG("type", DebuggerEnv_getType, 0),
JS_PSG("object", DebuggerEnv_getObject, 0),
JS_PSG("parent", DebuggerEnv_getParent, 0),
JS_PS_END
};
static JSFunctionSpec DebuggerEnv_methods[] = {
JS_FN("names", DebuggerEnv_names, 0, 0),
JS_FN("find", DebuggerEnv_find, 1, 0),
JS_FS_END
};
/*** Glue ****************************************************************************************/
@ -3504,8 +3822,16 @@ JS_DefineDebuggerObject(JSContext *cx, JSObject *obj)
if (!objectProto)
return false;
JSObject *envProto = js_InitClass(cx, debugCtor, objProto, &DebuggerEnv_class,
DebuggerEnv_construct, 0,
DebuggerEnv_properties, DebuggerEnv_methods,
NULL, NULL);
if (!envProto)
return false;
debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_FRAME_PROTO, ObjectValue(*frameProto));
debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_OBJECT_PROTO, ObjectValue(*objectProto));
debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SCRIPT_PROTO, ObjectValue(*scriptProto));
debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_ENV_PROTO, ObjectValue(*envProto));
return true;
}

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

@ -72,6 +72,7 @@ class Debugger {
enum {
JSSLOT_DEBUG_PROTO_START,
JSSLOT_DEBUG_FRAME_PROTO = JSSLOT_DEBUG_PROTO_START,
JSSLOT_DEBUG_ENV_PROTO,
JSSLOT_DEBUG_OBJECT_PROTO,
JSSLOT_DEBUG_SCRIPT_PROTO,
JSSLOT_DEBUG_PROTO_STOP,
@ -105,13 +106,16 @@ class Debugger {
FrameMap;
FrameMap frames;
/* An ephemeral map from JSScript* to Debugger.Script instances. */
typedef WeakMap<HeapPtrScript, HeapPtrObject> ScriptWeakMap;
ScriptWeakMap scripts;
/* The map from debuggee objects to their Debugger.Object instances. */
typedef WeakMap<HeapPtrObject, HeapPtrObject> ObjectWeakMap;
ObjectWeakMap objects;
/* An ephemeral map from JSScript* to Debugger.Script instances. */
typedef WeakMap<HeapPtrScript, HeapPtrObject> ScriptWeakMap;
ScriptWeakMap scripts;
/* The map from debuggee Envs to Debugger.Environment instances. */
ObjectWeakMap environments;
bool addDebuggeeGlobal(JSContext *cx, GlobalObject *obj);
void removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
@ -272,6 +276,13 @@ class Debugger {
inline bool observesGlobal(GlobalObject *global) const;
inline bool observesFrame(StackFrame *fp) const;
/*
* If env is NULL, call vp->setNull() and return true. Otherwise, find or
* create a Debugger.Environment object for the given Env. On success,
* store the Environment object in *vp and return true.
*/
bool wrapEnvironment(JSContext *cx, Env *env, Value *vp);
/*
* Like cx->compartment->wrap(cx, vp), but for the debugger compartment.
*
@ -518,8 +529,8 @@ Debugger::onNewScript(JSContext *cx, JSScript *script, GlobalObject *compileAndG
}
extern JSBool
EvaluateInScope(JSContext *cx, JSObject *scobj, StackFrame *fp, const jschar *chars,
uintN length, const char *filename, uintN lineno, Value *rval);
EvaluateInEnv(JSContext *cx, Env *env, StackFrame *fp, const jschar *chars,
uintN length, const char *filename, uintN lineno, Value *rval);
}