diff --git a/js/src/jit-test/tests/debug/Environment-01.js b/js/src/jit-test/tests/debug/Environment-01.js
new file mode 100644
index 00000000000..08f18aea7d2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-01.js
@@ -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);
diff --git a/js/src/jit-test/tests/debug/Environment-02.js b/js/src/jit-test/tests/debug/Environment-02.js
new file mode 100644
index 00000000000..d05c56f5940
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-02.js
@@ -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);
diff --git a/js/src/jit-test/tests/debug/Environment-find-01.js b/js/src/jit-test/tests/debug/Environment-find-01.js
new file mode 100644
index 00000000000..684ffe9e5bb
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-find-01.js
@@ -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);
diff --git a/js/src/jit-test/tests/debug/Environment-find-02.js b/js/src/jit-test/tests/debug/Environment-find-02.js
new file mode 100644
index 00000000000..ef4e058d6a0
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-find-02.js
@@ -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);
diff --git a/js/src/jit-test/tests/debug/Environment-find-03.js b/js/src/jit-test/tests/debug/Environment-find-03.js
new file mode 100644
index 00000000000..04bfb616749
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-find-03.js
@@ -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);
diff --git a/js/src/jit-test/tests/debug/Environment-find-04.js b/js/src/jit-test/tests/debug/Environment-find-04.js
new file mode 100644
index 00000000000..39f986c818f
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-find-04.js
@@ -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);
diff --git a/js/src/jit-test/tests/debug/Environment-find-05.js b/js/src/jit-test/tests/debug/Environment-find-05.js
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/js/src/jit-test/tests/debug/Environment-find-06.js b/js/src/jit-test/tests/debug/Environment-find-06.js
new file mode 100644
index 00000000000..f49dc31cf2e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-find-06.js
@@ -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);');
diff --git a/js/src/jit-test/tests/debug/Environment-gc-01.js b/js/src/jit-test/tests/debug/Environment-gc-01.js
new file mode 100644
index 00000000000..b9318f646c4
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-gc-01.js
@@ -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");
diff --git a/js/src/jit-test/tests/debug/Environment-gc-02.js b/js/src/jit-test/tests/debug/Environment-gc-02.js
new file mode 100644
index 00000000000..098a04c1d87
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-gc-02.js
@@ -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();
diff --git a/js/src/jit-test/tests/debug/Environment-identity-01.js b/js/src/jit-test/tests/debug/Environment-identity-01.js
new file mode 100644
index 00000000000..f7e76d1d87e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-identity-01.js
@@ -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);
diff --git a/js/src/jit-test/tests/debug/Environment-identity-02.js b/js/src/jit-test/tests/debug/Environment-identity-02.js
new file mode 100644
index 00000000000..0ab4b7d216c
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-identity-02.js
@@ -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);
diff --git a/js/src/jit-test/tests/debug/Environment-identity-03.js b/js/src/jit-test/tests/debug/Environment-identity-03.js
new file mode 100644
index 00000000000..9a020182006
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-identity-03.js
@@ -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 = <>>.(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));
diff --git a/js/src/jit-test/tests/debug/Environment-identity-04.js b/js/src/jit-test/tests/debug/Environment-identity-04.js
new file mode 100644
index 00000000000..a9d2b21dcd1
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-identity-04.js
@@ -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);
diff --git a/js/src/jit-test/tests/debug/Environment-names-01.js b/js/src/jit-test/tests/debug/Environment-names-01.js
new file mode 100644
index 00000000000..557e4f976ec
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-names-01.js
@@ -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);
diff --git a/js/src/jit-test/tests/debug/Environment-names-02.js b/js/src/jit-test/tests/debug/Environment-names-02.js
new file mode 100644
index 00000000000..0073cadcf85
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-names-02.js
@@ -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);
diff --git a/js/src/jit-test/tests/debug/Environment-parent-01.js b/js/src/jit-test/tests/debug/Environment-parent-01.js
new file mode 100644
index 00000000000..77e0ddb2380
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-parent-01.js
@@ -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);
diff --git a/js/src/jit-test/tests/debug/Environment-type-01.js b/js/src/jit-test/tests/debug/Environment-type-01.js
new file mode 100644
index 00000000000..695f6c3e1a2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Environment-type-01.js
@@ -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');
diff --git a/js/src/jit-test/tests/debug/Frame-environment-01.js b/js/src/jit-test/tests/debug/Frame-environment-01.js
new file mode 100644
index 00000000000..73b5582dc5e
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-environment-01.js
@@ -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();
diff --git a/js/src/jit-test/tests/debug/Frame-environment-02.js b/js/src/jit-test/tests/debug/Frame-environment-02.js
new file mode 100644
index 00000000000..c712c919ec2
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-environment-02.js
@@ -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()");
diff --git a/js/src/jit-test/tests/debug/Frame-environment-03.js b/js/src/jit-test/tests/debug/Frame-environment-03.js
new file mode 100644
index 00000000000..c7ed7eaa4ac
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-environment-03.js
@@ -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);
diff --git a/js/src/jit-test/tests/debug/Frame-environment-04.js b/js/src/jit-test/tests/debug/Frame-environment-04.js
new file mode 100644
index 00000000000..b41a7888f53
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-environment-04.js
@@ -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);
diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-10.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-10.js
index 20b2fc700f5..99e3cf3d48a 100644
--- a/js/src/jit-test/tests/debug/Frame-evalWithBindings-10.js
+++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-10.js
@@ -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;
diff --git a/js/src/jit-test/tests/debug/Object-environment-01.js b/js/src/jit-test/tests/debug/Object-environment-01.js
new file mode 100644
index 00000000000..168bc780bb6
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-environment-01.js
@@ -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);
+
diff --git a/js/src/jit-test/tests/debug/Object-environment-02.js b/js/src/jit-test/tests/debug/Object-environment-02.js
new file mode 100644
index 00000000000..5f1a849feef
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-environment-02.js
@@ -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);
diff --git a/js/src/js.msg b/js/src/js.msg
index 028555c226b..53f815d582a 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -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")
diff --git a/js/src/jscntxtinlines.h b/js/src/jscntxtinlines.h
index ce21f4c862d..c7e99e5592d 100644
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -227,7 +227,8 @@ class CompartmentChecker
}
void check(StackFrame *fp) {
- check(&fp->scopeChain());
+ if (fp)
+ check(&fp->scopeChain());
}
};
diff --git a/js/src/jsdbgapi.cpp b/js/src/jsdbgapi.cpp
index 9894805fc40..1763f6ee179 100644
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -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)
diff --git a/js/src/jsprvtd.h b/js/src/jsprvtd.h
index 1e400489dc0..0680106eb19 100644
--- a/js/src/jsprvtd.h
+++ b/js/src/jsprvtd.h
@@ -243,6 +243,14 @@ typedef HashMap
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;
diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp
index e1f6afc25d7..8316bfdba28 100644
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -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, 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 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(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;
}
diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h
index 5daf9855889..fcbece278eb 100644
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -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 ScriptWeakMap;
+ ScriptWeakMap scripts;
+
/* The map from debuggee objects to their Debugger.Object instances. */
typedef WeakMap ObjectWeakMap;
ObjectWeakMap objects;
- /* An ephemeral map from JSScript* to Debugger.Script instances. */
- typedef WeakMap 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);
}