зеркало из https://github.com/mozilla/pjs.git
A first cut at Debugger.Environment. Bug 690558, r=jimb.
--HG-- extra : rebase_source : 3cafc4e635f11b3219285fdf6fad952c15765f96
This commit is contained in:
Родитель
c510997b92
Коммит
1257a0203a
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче