diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /js/src/jit-test/tests/debug | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'js/src/jit-test/tests/debug')
769 files changed, 20687 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/debug/DebuggeeWouldRun-01.js b/js/src/jit-test/tests/debug/DebuggeeWouldRun-01.js new file mode 100644 index 000000000..315e3dcf4 --- /dev/null +++ b/js/src/jit-test/tests/debug/DebuggeeWouldRun-01.js @@ -0,0 +1,7 @@ +// Bug 1250190: Shouldn't crash. |jit-test| exitstatus: 3 + +g = newGlobal(); +var dbg = Debugger(g) +dbg.onNewPromise = () => g.makeFakePromise(); +g.makeFakePromise(); + diff --git a/js/src/jit-test/tests/debug/DebuggeeWouldRun-02.js b/js/src/jit-test/tests/debug/DebuggeeWouldRun-02.js new file mode 100644 index 000000000..290effcb9 --- /dev/null +++ b/js/src/jit-test/tests/debug/DebuggeeWouldRun-02.js @@ -0,0 +1,7 @@ +// Bug 1250190: Shouldn't crash. |jit-test| exitstatus: 3 + +var g = newGlobal(); +var dbg = Debugger(g) +dbg.onNewGlobalObject = () => g.newGlobal(); +g.newGlobal(); +print("yo"); diff --git a/js/src/jit-test/tests/debug/DebuggeeWouldRun-03.js b/js/src/jit-test/tests/debug/DebuggeeWouldRun-03.js new file mode 100644 index 000000000..8978fe0c4 --- /dev/null +++ b/js/src/jit-test/tests/debug/DebuggeeWouldRun-03.js @@ -0,0 +1,9 @@ +// Bug 1250190: Shouldn't crash. |jit-test| error: yadda + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onNewGlobalObject = function () { + dbg.onNewGlobalObject = function () { throw "yadda"; }; + newGlobal(); +} +newGlobal(); diff --git a/js/src/jit-test/tests/debug/DebuggeeWouldRun-04.js b/js/src/jit-test/tests/debug/DebuggeeWouldRun-04.js new file mode 100644 index 000000000..6ed831b0d --- /dev/null +++ b/js/src/jit-test/tests/debug/DebuggeeWouldRun-04.js @@ -0,0 +1,9 @@ +// Bug 1250190: Shouldn't crash. |jit-test| error: yadda + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onNewScript = function () { + dbg.onNewScript = function () { throw "yadda"; }; + g.Function("noodles;"); +} +g.Function("poodles;"); diff --git a/js/src/jit-test/tests/debug/Debugger-add-Debugger-prototype.js b/js/src/jit-test/tests/debug/Debugger-add-Debugger-prototype.js new file mode 100644 index 000000000..ba98347a7 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-add-Debugger-prototype.js @@ -0,0 +1,6 @@ +load(libdir + "asserts.js"); + +assertThrowsInstanceOf(function () { + var dbg = new Debugger(); + dbg.addDebuggee(Debugger.Object.prototype); +}, TypeError);
\ No newline at end of file diff --git a/js/src/jit-test/tests/debug/Debugger-adoptDebuggeeValue.js b/js/src/jit-test/tests/debug/Debugger-adoptDebuggeeValue.js new file mode 100644 index 000000000..0acdb6167 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-adoptDebuggeeValue.js @@ -0,0 +1,39 @@ +// simplest possible test of Debugger.adoptDebuggeeValue + +load(libdir + "asserts.js"); + +var g = newGlobal(); + +var dbg1 = new Debugger(); +var gDO1 = dbg1.addDebuggee(g); +var obj1 = gDO1.executeInGlobal("({})").return; + +var dbg2 = Debugger(g); +var gDO2 = dbg2.addDebuggee(g); +var obj2 = gDO2.executeInGlobal("({})").return; + +assertThrowsInstanceOf(function () { + obj1.defineProperty("foo", { + configurable: true, + enumerable: true, + value: obj2, + writable: true + }); +}, Error); + +let obj3 = dbg1.adoptDebuggeeValue(obj2); + +obj1.defineProperty("foo", { + configurable: true, + enumerable: true, + value: obj3, + writable: true +}); + +assertThrowsInstanceOf(function () { + dbg1.adoptDebuggeeValue({}); +}, TypeError); + +assertThrowsInstanceOf(function () { + dbg1.adoptDebuggeeValue(Debugger.Object.prototype); +}, TypeError); diff --git a/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-01.js b/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-01.js new file mode 100644 index 000000000..836781a19 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-01.js @@ -0,0 +1,34 @@ +load(libdir + "asm.js"); + +var g = newGlobal(); +g.parent = this; +g.eval("dbg = new Debugger(parent);"); + +// Initial state is to inhibit asm.js. +assertEq(g.dbg.allowUnobservedAsmJS, false); + +var asmFunStr = USE_ASM + 'function f() {} return f'; + +// With asm.js inhibited, asm.js should fail with a type error about the +// debugger being on. +assertAsmTypeFail(asmFunStr); + +// With asm.js uninhibited, asm.js linking should work. +g.dbg.allowUnobservedAsmJS = true; +assertEq(asmLink(asmCompile(asmFunStr))(), undefined); + +// Toggling back should inhibit again. +g.dbg.allowUnobservedAsmJS = false; +assertAsmTypeFail(asmFunStr); + +// Disabling the debugger should uninhibit. +g.dbg.enabled = false; +assertEq(asmLink(asmCompile(asmFunStr))(), undefined); + +// Enabling it should inhibit again. +g.dbg.enabled = true; +assertAsmTypeFail(asmFunStr); + +// Removing the global should lift the inhibition. +g.dbg.removeDebuggee(this); +assertEq(asmLink(asmCompile(asmFunStr))(), undefined); diff --git a/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-02.js b/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-02.js new file mode 100644 index 000000000..f77f42978 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-allowUnobservedAsmJS-02.js @@ -0,0 +1,25 @@ +// Debugger.allowUnobservedAsmJS with off-thread parsing. + +load(libdir + "asm.js"); + +if (helperThreadCount() == 0) + quit(); + +var g = newGlobal(); +g.parent = this; +g.eval("dbg = new Debugger(parent);"); + +assertEq(g.dbg.allowUnobservedAsmJS, false); + +enableLastWarning(); + +var asmFunStr = USE_ASM + 'function f() {} return f'; +offThreadCompileScript("(function() {" + asmFunStr + "})"); +runOffThreadScript(); + +var msg = getLastWarning().message; +assertEq(msg === "asm.js type error: Disabled by debugger" || + msg === "asm.js type error: Disabled by lack of a JIT compiler" || + msg === "asm.js type error: Disabled by 'asmjs' runtime option" || + msg === "asm.js type error: Disabled by lack of compiler support", + true); diff --git a/js/src/jit-test/tests/debug/Debugger-clearAllBreakpoints-01.js b/js/src/jit-test/tests/debug/Debugger-clearAllBreakpoints-01.js new file mode 100644 index 000000000..d61c9d8a1 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-clearAllBreakpoints-01.js @@ -0,0 +1,29 @@ +// clearAllBreakpoints clears breakpoints for the current Debugger object only. + +var g = newGlobal(); + +var hits = 0; +function attach(i) { + var dbg = Debugger(g); + var handler = { + hit: function (frame) { + hits++; + dbg.clearAllBreakpoints(); + } + }; + + dbg.onDebuggerStatement = function (frame) { + var s = frame.script; + var offs = s.getLineOffsets(g.line0 + 3); + for (var i = 0; i < offs.length; i++) + s.setBreakpoint(offs[i], handler); + }; +} +for (var i = 0; i < 4; i++) + attach(i); + +g.eval("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "for (var i = 0; i < 7; i++)\n" + // line0 + 2 + " Math.sin(0);\n"); // line0 + 3 +assertEq(hits, 4); diff --git a/js/src/jit-test/tests/debug/Debugger-ctor-01.js b/js/src/jit-test/tests/debug/Debugger-ctor-01.js new file mode 100644 index 000000000..3136f01dd --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-ctor-01.js @@ -0,0 +1,21 @@ +load(libdir + 'asserts.js'); + +// Debugger rejects arguments that aren't cross-compartment wrappers. +assertThrowsInstanceOf(function () { Debugger(null); }, TypeError); +assertThrowsInstanceOf(function () { Debugger(true); }, TypeError); +assertThrowsInstanceOf(function () { Debugger(42); }, TypeError); +assertThrowsInstanceOf(function () { Debugger("bad"); }, TypeError); +assertThrowsInstanceOf(function () { Debugger(function () {}); }, TypeError); +assertThrowsInstanceOf(function () { Debugger(this); }, TypeError); +assertThrowsInstanceOf(function () { new Debugger(null); }, TypeError); +assertThrowsInstanceOf(function () { new Debugger(true); }, TypeError); +assertThrowsInstanceOf(function () { new Debugger(42); }, TypeError); +assertThrowsInstanceOf(function () { new Debugger("bad"); }, TypeError); +assertThrowsInstanceOf(function () { new Debugger(function () {}); }, TypeError); +assertThrowsInstanceOf(function () { new Debugger(this); }, TypeError); + +// From the main compartment, creating a Debugger on a sandbox compartment. +var g = newGlobal(); +var dbg = new Debugger(g); +assertEq(dbg instanceof Debugger, true); +assertEq(Object.getPrototypeOf(dbg), Debugger.prototype); diff --git a/js/src/jit-test/tests/debug/Debugger-ctor-02.js b/js/src/jit-test/tests/debug/Debugger-ctor-02.js new file mode 100644 index 000000000..2095f9beb --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-ctor-02.js @@ -0,0 +1,13 @@ +// Test creating a Debugger in a sandbox, debugging the initial global. + +load(libdir + 'asserts.js'); + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.eval("var dbg = new Debugger(debuggeeGlobal);"); +assertEq(g.eval("dbg instanceof Debugger"), true); + +// The Debugger constructor from this compartment will not accept as its argument +// an Object from this compartment. Shenanigans won't fool the membrane. +g.parent = this; +assertThrowsInstanceOf(function () { g.eval("parent.Debugger(parent.Object())"); }, TypeError); diff --git a/js/src/jit-test/tests/debug/Debugger-ctor-03.js b/js/src/jit-test/tests/debug/Debugger-ctor-03.js new file mode 100644 index 000000000..f2245e7c9 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-ctor-03.js @@ -0,0 +1,19 @@ +// If the debuggee cannot be put into debug mode, throw. + +// Run this test only if this compartment can't be put into debug mode. +var canEnable = true; +if (typeof setDebugMode === 'function') { + try { + setDebugMode(true); + } catch (exc) { + canEnable = false; + } +} + +if (!canEnable) { + var g = newGlobal(); + g.libdir = libdir; + g.eval("load(libdir + 'asserts.js');"); + g.parent = this; + g.eval("assertThrowsInstanceOf(function () { new Debugger(parent); }, Error);"); +} diff --git a/js/src/jit-test/tests/debug/Debugger-ctor-04.js b/js/src/jit-test/tests/debug/Debugger-ctor-04.js new file mode 100644 index 000000000..6d3dbbb3c --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-ctor-04.js @@ -0,0 +1,5 @@ +// Repeated Debugger() arguments are ignored. + +var g = newGlobal(); +var dbg = Debugger(g, g, g); +assertEq(dbg.getDebuggees().length, 1); diff --git a/js/src/jit-test/tests/debug/Debugger-ctor-05.js b/js/src/jit-test/tests/debug/Debugger-ctor-05.js new file mode 100644 index 000000000..6c1f4750f --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-ctor-05.js @@ -0,0 +1,8 @@ +// Redundant non-global Debugger() arguments are ignored. + +var g = newGlobal(); +g.eval("var a = {}, b = {};"); +var dbg = Debugger(g.a, g.b); +var arr = dbg.getDebuggees(); +assertEq(arr.length, 1); +assertEq(arr[0], dbg.addDebuggee(g)); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-01.js b/js/src/jit-test/tests/debug/Debugger-debuggees-01.js new file mode 100644 index 000000000..b8a22c029 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-01.js @@ -0,0 +1,5 @@ +// A Debugger object created with no argument initially has no debuggees. +var dbg = new Debugger; +var debuggees = dbg.getDebuggees(); +assertEq(Array.isArray(debuggees), true); +assertEq(debuggees.length, 0); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-02.js b/js/src/jit-test/tests/debug/Debugger-debuggees-02.js new file mode 100644 index 000000000..e8b23b7e9 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-02.js @@ -0,0 +1,10 @@ +// The array returned by getDebuggees is just a snapshot, not live. +var dbg = new Debugger; +var a1 = dbg.getDebuggees(); +var g = newGlobal(); +var gw = dbg.addDebuggee(g); +assertEq(gw instanceof Debugger.Object, true); +var a2 = dbg.getDebuggees(); +assertEq(a2.length, 1); +assertEq(a2[0], gw); +assertEq(a1.length, 0); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-03.js b/js/src/jit-test/tests/debug/Debugger-debuggees-03.js new file mode 100644 index 000000000..315985668 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-03.js @@ -0,0 +1,34 @@ +// Debugger hooks fire based on debuggees. + +var g1 = newGlobal(); +g1.eval("var g2 = newGlobal('same-compartment')"); +var g2 = g1.g2; +g1.eval("function f() { debugger; g2.g(); }"); +g2.eval("function g() { debugger; }"); + +var log; +var dbg = new Debugger; +dbg.onDebuggerStatement = function (frame) { log += frame.callee.name; }; + +// No debuggees: onDebuggerStatement is not called. +log = ''; +g1.f(); +assertEq(log, ''); + +// Add a debuggee and check that the handler is called. +var g1w = dbg.addDebuggee(g1); +log = ''; +g1.f(); +assertEq(log, 'f'); + +// Two debuggees, two onDebuggerStatement calls. +dbg.addDebuggee(g2); +log = ''; +g1.f(); +assertEq(log, 'fg'); + +// After a debuggee is removed, it no longer calls hooks. +assertEq(dbg.removeDebuggee(g1w), undefined); +log = ''; +g1.f(); +assertEq(log, 'g'); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-04.js b/js/src/jit-test/tests/debug/Debugger-debuggees-04.js new file mode 100644 index 000000000..45f5b1f52 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-04.js @@ -0,0 +1,26 @@ +// hasDebuggee tests. + +var g1 = newGlobal(), g1w; +g1.eval("var g2 = newGlobal('same-compartment')"); +var g2 = g1.g2; +var g1w, g2w; + +var dbg = new Debugger; +function checkHas(hasg1, hasg2) { + assertEq(dbg.hasDebuggee(g1), hasg1); + if (typeof g1w === 'object') + assertEq(dbg.hasDebuggee(g1w), hasg1); + assertEq(dbg.hasDebuggee(g2), hasg2); + if (typeof g2w === 'object') + assertEq(dbg.hasDebuggee(g2w), hasg2); +} + +checkHas(false, false); +g1w = dbg.addDebuggee(g1); +checkHas(true, false); +g2w = dbg.addDebuggee(g2); +checkHas(true, true); +dbg.removeDebuggee(g1w); +checkHas(false, true); +dbg.removeDebuggee(g2); +checkHas(false, false); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-05.js b/js/src/jit-test/tests/debug/Debugger-debuggees-05.js new file mode 100644 index 000000000..569594de9 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-05.js @@ -0,0 +1,8 @@ +// addDebuggee returns different Debugger.Object wrappers for different Debugger objects. + +var g = newGlobal(); +var dbg1 = new Debugger; +var gw1 = dbg1.addDebuggee(g); +var dbg2 = new Debugger; +var gw2 = dbg2.addDebuggee(g); +assertEq(gw1 !== gw2, true); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-06.js b/js/src/jit-test/tests/debug/Debugger-debuggees-06.js new file mode 100644 index 000000000..39e00f484 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-06.js @@ -0,0 +1,27 @@ +// {has,add,remove}Debuggee throw a TypeError if the argument is invalid. + +load(libdir + "asserts.js"); + +var dbg = new Debugger; + +function check(val) { + assertThrowsInstanceOf(function () { dbg.hasDebuggee(val); }, TypeError); + assertThrowsInstanceOf(function () { dbg.addDebuggee(val); }, TypeError); + assertThrowsInstanceOf(function () { dbg.removeDebuggee(val); }, TypeError); +} + +// Primitive values are invalid. +check(undefined); +check(null); +check(false); +check(1); +check(NaN); +check("ok"); +check(Symbol("ok")); + +// A Debugger.Object that belongs to a different Debugger object is invalid. +var g = newGlobal(); +var dbg2 = new Debugger; +var w = dbg2.addDebuggee(g); +assertEq(w instanceof Debugger.Object, true); +check(w); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-08.js b/js/src/jit-test/tests/debug/Debugger-debuggees-08.js new file mode 100644 index 000000000..0aca89676 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-08.js @@ -0,0 +1,25 @@ +// Adding a debuggee more than once is redundant. + +var dbg = new Debugger; +var g = newGlobal(); +var w = dbg.addDebuggee(g); +assertEq(w instanceof Debugger.Object, true); + +function usual() { + assertEq(dbg.hasDebuggee(g), true); + assertEq(dbg.hasDebuggee(w), true); + var arr = dbg.getDebuggees(); + assertEq(arr.length, 1); + assertEq(arr[0], w); +} + +usual(); +assertEq(dbg.addDebuggee(g), w); +usual(); +assertEq(dbg.addDebuggee(w), w); +usual(); + +// Removing the debuggee once is enough. +assertEq(dbg.removeDebuggee(g), undefined); +assertEq(dbg.hasDebuggee(g), false); +assertEq(dbg.getDebuggees().length, 0); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-09.js b/js/src/jit-test/tests/debug/Debugger-debuggees-09.js new file mode 100644 index 000000000..95b4b77b3 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-09.js @@ -0,0 +1,21 @@ +// If hasDebuggee(x) is false, removeDebuggee(x) does nothing. + +var dbg = new Debugger; + +function check(obj) { + // If obj is something we could never debug, hasDebuggee(obj) is false. + assertEq(dbg.hasDebuggee(obj), false); + + // If hasDebuggee(x) is false, removeDebuggee(x) does nothing. + assertEq(dbg.removeDebuggee(obj), undefined); +} + +// global objects which happen not to be debuggees at the moment +var g1 = newGlobal('same-compartment'); +check(g1); + +// objects in a compartment that is already debugging us +var g2 = newGlobal(); +g2.parent = this; +g2.eval("var dbg = new Debugger(parent);"); +check(g2); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-10.js b/js/src/jit-test/tests/debug/Debugger-debuggees-10.js new file mode 100644 index 000000000..03af30c7d --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-10.js @@ -0,0 +1,18 @@ +// Allow diamonds in the graph of the compartment "debugs" relation. +var program = newGlobal(); +var d1 = newGlobal(); +d1.top = this; +var d2 = newGlobal(); +d2.top = this; +var dbg = new Debugger(d1, d2); +d1.eval("var dbg = new Debugger(top.program)"); +d2.eval("var dbg = new Debugger(top.program)"); + +// mess with the edges a little bit -- all this should be fine, no cycles +d1.dbg.removeDebuggee(program); +d1.dbg.addDebuggee(program); +dbg.addDebuggee(program); +d1.dbg.addDebuggee(d2); +dbg.removeDebuggee(d2); +dbg.addDebuggee(d2); + diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-11.js b/js/src/jit-test/tests/debug/Debugger-debuggees-11.js new file mode 100644 index 000000000..04035c59b --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-11.js @@ -0,0 +1,22 @@ +// Don't allow cycles in the graph of the compartment "debugs" relation. + +load(libdir + "asserts.js"); + +// trivial cycles +var dbg = new Debugger; +assertThrowsInstanceOf(function () { dbg.addDebuggee(this); }, TypeError); +assertThrowsInstanceOf(function () { new Debugger(this); }, TypeError); + +// cycles of length 2 +var d1 = newGlobal(); +d1.top = this; +d1.eval("var dbg = new Debugger(top)"); +assertThrowsInstanceOf(function () { dbg.addDebuggee(d1); }, TypeError); +assertThrowsInstanceOf(function () { new Debugger(d1); }, TypeError); + +// cycles of length 3 +var d2 = newGlobal(); +d2.top = this; +d2.eval("var dbg = new Debugger(top.d1)"); +assertThrowsInstanceOf(function () { dbg.addDebuggee(d2); }, TypeError); +assertThrowsInstanceOf(function () { new Debugger(d2); }, TypeError); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-12.js b/js/src/jit-test/tests/debug/Debugger-debuggees-12.js new file mode 100644 index 000000000..86870f713 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-12.js @@ -0,0 +1,10 @@ +// Events in a non-debuggee are ignored, even if a debuggee is in the same compartment. +var g1 = newGlobal(); +var g2 = g1.eval("newGlobal('same-compartment')"); +var dbg = new Debugger(g1); +var hits = 0; +dbg.onDebuggerStatement = function () { hits++; }; +g1.eval("debugger;"); +assertEq(hits, 1); +g2.eval("debugger;"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-13.js b/js/src/jit-test/tests/debug/Debugger-debuggees-13.js new file mode 100644 index 000000000..de9262cba --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-13.js @@ -0,0 +1,9 @@ +// Removing a debuggee does not detach the debugger from a compartment if another debuggee is in it. +var g1 = newGlobal(); +var g2 = g1.eval("newGlobal('same-compartment')"); +var dbg = new Debugger(g1, g2); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { hits++; }; +dbg.removeDebuggee(g1); +g2.eval("debugger;"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-14.js b/js/src/jit-test/tests/debug/Debugger-debuggees-14.js new file mode 100644 index 000000000..a87c9bdba --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-14.js @@ -0,0 +1,8 @@ +// Adding a debuggee in a compartment that is already in debug mode works +// even if a script from that compartment is on the stack. + +var g = newGlobal(); +var dbg1 = Debugger(g); +var dbg2 = Debugger(); +g.parent = this; +g.eval("parent.dbg2.addDebuggee(this);"); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-15.js b/js/src/jit-test/tests/debug/Debugger-debuggees-15.js new file mode 100644 index 000000000..9270b282e --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-15.js @@ -0,0 +1,7 @@ +// Debugger mode can be disabled for a compartment even if it has scripts running. +var g = newGlobal(); +var dbg = Debugger(g); +g.parent = this; +var n = 2; +g.eval("parent.dbg.removeDebuggee(this); parent.n += 2"); +assertEq(n, 4); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-16.js b/js/src/jit-test/tests/debug/Debugger-debuggees-16.js new file mode 100644 index 000000000..4221cb827 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-16.js @@ -0,0 +1,30 @@ +// GC can turn off debug mode in a compartment. + +var dbgs = []; +var nonDebugGlobals = []; +var f = gc; +for (var i = 0; i < 4; i++) { + // Create two globals, one debuggee. + var g1 = newGlobal(); + var g2 = g1.eval("newGlobal('same-compartment')"); + var dbg = Debugger(g1); + dbg.onDebuggerStatement = function () {}; + + // Thread a chain of functions through the non-debuggee globals. + g2.eval("function f() { return g() + 1; }"); + g2.g = f; + f = g2.f; + + // Root the Debugger objects and non-debuggee globals. + dbgs[i] = dbg; + nonDebugGlobals[i] = g2; +} + +// Call the chain of functions. At the end of the chain is gc. This will +// collect (some or all of) the debuggee globals, leaving non-debuggee +// globals. It should disable debug mode in those debuggee compartments. +nonDebugGlobals[nonDebugGlobals.length - 1].f(); + +gc(); +nonDebugGlobals[0].g = function () { return 0; } +assertEq(nonDebugGlobals[nonDebugGlobals.length - 1].f(), nonDebugGlobals.length); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-17.js b/js/src/jit-test/tests/debug/Debugger-debuggees-17.js new file mode 100644 index 000000000..0e3408ead --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-17.js @@ -0,0 +1,26 @@ +// addDebuggee, hasDebuggee, and removeDebuggee all throw if presented with +// objects that are not valid global object designators. + +load(libdir + 'asserts.js'); + +var dbg = new Debugger; + +function check(bad) { + print("check(" + uneval(bad) + ")"); + assertThrowsInstanceOf(function () { dbg.addDebuggee(bad); }, TypeError); + assertEq(dbg.getDebuggees().length, 0); + assertThrowsInstanceOf(function () { dbg.hasDebuggee(bad); }, TypeError); + assertThrowsInstanceOf(function () { dbg.removeDebuggee(bad); }, TypeError); +} + +var g = newGlobal(); +check(g.Object()); +check(g.Object); +check(g.Function("")); + +// A Debugger.Object belonging to a different Debugger is not a valid way +// to designate a global, even if its referent is a global. +var g2 = newGlobal(); +var dbg2 = new Debugger; +var d2g2 = dbg2.addDebuggee(g2); +check(d2g2); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-18.js b/js/src/jit-test/tests/debug/Debugger-debuggees-18.js new file mode 100644 index 000000000..e119b6ba0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-18.js @@ -0,0 +1,117 @@ +// Debugger.prototype.{addDebuggee,hasDebuggee,removeDebuggee} recognize globals +// regardless of how they are specified. + +var dbg = new Debugger; + +// Assert that dbg's debuggees are exactly the set passed as arguments. +// The arguments are assumed to be Debugger.Object instances referring to +// globals without wrappers --- which is the sort returned by addDebuggee. +function assertDebuggees() { + print("assertDebuggees([" + Array.prototype.slice.call(arguments).map((g) => g.toSource()) + "])"); + var debuggees = dbg.getDebuggees(); + assertEq(arguments.length, debuggees.length); + for each (g in arguments) + assertEq(debuggees.indexOf(g) != -1, true); +} + +var g1 = newGlobal(); g1.toSource = function () { return "[global g1]"; }; +var g2 = newGlobal(); g2.toSource = function () { return "[global g2]"; }; + +assertDebuggees(); + +// Produce every possible way to designate g1, for us to play with. +// Globals can be designated by any of the following: +// +// - "CCW": a Cross-Compartment Wrapper (CCW) of a global object +// - "D.O": a Debugger.Object whose referent is a global object +// - "D.O of CCW": a Debugger.Object whose referent is a CCW of a +// global object, where the CCW can be securely unwrapped +// +// There's no direct "G", since globals are always in their own +// compartments, never the debugger's; if we ever viewed them directly, +// that would be a compartment violation. + +// "dg1" means "Debugger.Object referring (directly) to g1". +var dg1 = dbg.addDebuggee(g1); +dg1.toSource = function() { return "[Debugger.Object for global g1]"; }; +assertEq(dg1.global, dg1); +assertEq(dg1.unwrap(), dg1); +assertDebuggees(dg1); + +// We need to add g2 as a debuggee; that's the only way to get a D.O referring +// to it without a wrapper. +var dg2 = dbg.addDebuggee(g2); +dg2.toSource = function() { return "[Debugger.Object for global g2]"; }; +assertEq(dg2.global, dg2); +assertEq(dg2.unwrap(), dg2); +assertDebuggees(dg1, dg2); + +// "dwg1" means "Debugger.Object referring to CCW of g1". +var dwg1 = dg2.makeDebuggeeValue(g1); +assertEq(dwg1.global, dg2); +assertEq(dwg1.unwrap(), dg1); +dwg1.toSource = function() { return "[Debugger.Object for CCW of global g1]"; }; + +assertDebuggees(dg1, dg2); +assertEq(dbg.removeDebuggee(g1), undefined); +assertEq(dbg.removeDebuggee(g2), undefined); +assertDebuggees(); + +// Systematically cover all the single-global possibilities: +// +// | added as | designated as | addDebuggee | hasDebuggee | removeDebuggee | +// |-------------+---------------+-------------+-------------+----------------| +// | (not added) | CCW | X | X | X | +// | | D.O | X | X | X | +// | | D.O of CCW | X | X | X | +// |-------------+---------------+-------------+-------------+----------------| +// | CCW | CCW | X | X | X | +// | | D.O | X | X | X | +// | | D.O of CCW | X | X | X | +// |-------------+---------------+-------------+-------------+----------------| +// | D.O | CCW | X | X | X | +// | | D.O | X | X | X | +// | | D.O of CCW | X | X | X | +// |-------------+---------------+-------------+-------------+----------------| +// | D.O of CCW | CCW | X | X | X | +// | | D.O | X | X | X | +// | | D.O of CCW | X | X | X | + +// Cover the "(not added)" section of the table, other than "addDebuggee": +assertEq(dbg.hasDebuggee(g1), false); +assertEq(dbg.hasDebuggee(dg1), false); +assertEq(dbg.hasDebuggee(dwg1), false); + +assertEq(dbg.removeDebuggee(g1), undefined); assertDebuggees(); +assertEq(dbg.removeDebuggee(dg1), undefined); assertDebuggees(); +assertEq(dbg.removeDebuggee(dwg1), undefined); assertDebuggees(); + +// Try all operations adding the debuggee using |addAs|, and operating on it +// using |designateAs|, thereby covering one row of the table (outside the '(not +// added)' section), and one case in the '(not added)', 'designated as' section. +// +// |Direct| should be the Debugger.Object referring directly to the debuggee +// global, for checking the results from addDebuggee and getDebuggees. +function combo(addAs, designateAs, direct) { + print("combo(" + uneval(addAs) + ", " + uneval(designateAs) + ")"); + assertDebuggees(); + assertEq(dbg.addDebuggee(addAs), direct); + assertDebuggees(direct); + assertEq(dbg.addDebuggee(designateAs), direct); + assertDebuggees(direct); + assertEq(dbg.hasDebuggee(designateAs), true); + assertEq(dbg.removeDebuggee(designateAs), undefined); + assertDebuggees(); +} + +combo(g1, g1, dg1); +combo(dg1, g1, dg1); +combo(dwg1, g1, dg1); + +combo(g1, dg1, dg1); +combo(dg1, dg1, dg1); +combo(dwg1, dg1, dg1); + +combo(g1, dwg1, dg1); +combo(dg1, dwg1, dg1); +combo(dwg1, dwg1, dg1); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-19.js b/js/src/jit-test/tests/debug/Debugger-debuggees-19.js new file mode 100644 index 000000000..3ef59cd1c --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-19.js @@ -0,0 +1,49 @@ +// removeAllDebuggees removes all the debuggees. + +var dbg = new Debugger; + +// If we eval in a debuggee, log which debuggee it was. +var log; +dbg.onEnterFrame = function (frame) { + log += 'e'; + // frame.environment in all evals below is the global lexical env. + log += frame.environment.parent.object.label; +}; + +var g1 = newGlobal(); +log = ''; +g1.eval('Math'); +assertEq(log, ''); // not yet a debuggee + +var g1w = dbg.addDebuggee(g1); +assertEq(g1w instanceof Debugger.Object, true); +g1w.label = 'g1'; +log = ''; +g1.eval('Math'); // now a debuggee +assertEq(log, 'eg1'); + +var g2 = newGlobal(); +log = ''; +g1.eval('Math'); // debuggee +g2.eval('Math'); // not a debuggee +assertEq(log, 'eg1'); + +var g2w = dbg.addDebuggee(g2); +assertEq(g2w instanceof Debugger.Object, true); +g2w.label = 'g2'; +log = ''; +g1.eval('Math'); // debuggee +g2.eval('this'); // debuggee +assertEq(log, 'eg1eg2'); + +var a1 = dbg.getDebuggees(); +assertEq(a1.length, 2); + +assertEq(dbg.removeAllDebuggees(), undefined); +var a2 = dbg.getDebuggees(); +assertEq(a2.length, 0); + +log = ''; +g1.eval('Math'); // no longer a debuggee +g2.eval('this'); // no longer a debuggee +assertEq(log, ''); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-20.js b/js/src/jit-test/tests/debug/Debugger-debuggees-20.js new file mode 100644 index 000000000..121698ab4 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-20.js @@ -0,0 +1,31 @@ +// addAllGlobalsAsDebuggees adds all the globals as debuggees. + +var g1 = newGlobal(); // Created before the Debugger; debuggee. +var g2 = newGlobal(); // Created before the Debugger; not debuggee. + +var dbg = new Debugger; + +var g3 = newGlobal(); // Created after the Debugger; debuggee. +var g4 = newGlobal(); // Created after the Debugger; not debuggee. + +var g1w = dbg.addDebuggee(g1); +var g3w = dbg.addDebuggee(g3); +assertEq(dbg.addAllGlobalsAsDebuggees(), undefined); + +// Get Debugger.Objects viewing the globals from their own compartments; +// this is the sort that findAllGlobals and addDebuggee return. +assertEq(g1w, g3w.makeDebuggeeValue(g1).unwrap()); +assertEq(g3w, g1w.makeDebuggeeValue(g3).unwrap()); + +var g2w = g1w.makeDebuggeeValue(g2).unwrap(); +var g4w = g1w.makeDebuggeeValue(g4).unwrap(); + +var thisw = g1w.makeDebuggeeValue(this).unwrap(); + +// Check that they're all there. +assertEq(dbg.hasDebuggee(g1w), true); +assertEq(dbg.hasDebuggee(g2w), true); +assertEq(dbg.hasDebuggee(g3w), true); +assertEq(dbg.hasDebuggee(g4w), true); +// The debugger's global is not a debuggee. +assertEq(dbg.hasDebuggee(thisw), false); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-21.js b/js/src/jit-test/tests/debug/Debugger-debuggees-21.js new file mode 100644 index 000000000..fb5f1b849 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-21.js @@ -0,0 +1,12 @@ +// Errors adding globals in addAllGlobalsAsDebuggees should be reported. + +// The exception that might be thrown in this test reflects our inability +// to change compartments to debug mode while they have frames on the +// stack. If we run this test with --debugjit, it won't throw an error at +// all, since all compartments are already in debug mode. So, pass if the +// script completes normally, or throws an appropriate exception. +try { + newGlobal().eval("(new Debugger).addAllGlobalsAsDebuggees();"); +} catch (ex) { + assertEq(!!(''+ex).match(/can't start debugging: a debuggee script is on the stack/), true); +} diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-22.js b/js/src/jit-test/tests/debug/Debugger-debuggees-22.js new file mode 100644 index 000000000..bf1dd638e --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-22.js @@ -0,0 +1,24 @@ +// Adding a debuggee allowed with scripts on stack. + +var g = newGlobal(); +g.dbg = new Debugger; + +g.eval("" + function f(d) { + g(d); + if (d) + assertEq(dbg.hasDebuggee(this), true); +}); + +g.eval("" + function g(d) { + if (!d) + return; + + dbg.addDebuggee(this); +}); + +g.eval("(" + function test() { + f(false); + f(false); + f(true); + f(true); +} + ")();"); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-23.js b/js/src/jit-test/tests/debug/Debugger-debuggees-23.js new file mode 100644 index 000000000..f8e85e45e --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-23.js @@ -0,0 +1,107 @@ +// Adding a debuggee allowed with scripts on stack from stranger places. + +// Test CCW. +(function testCCW() { + var g = newGlobal(); + var dbg = new Debugger; + g.dbg = dbg; + g.GLOBAL = g; + + g.turnOnDebugger = function () { + dbg.addDebuggee(g); + }; + + g.eval("" + function f(d) { + turnOnDebugger(); + assertEq(dbg.hasDebuggee(GLOBAL), true); + }); + + g.eval("(" + function test() { + f(false); + f(false); + f(true); + f(true); + } + ")();"); +})(); + +// Test getter. +(function testGetter() { + var g = newGlobal(); + g.dbg = new Debugger; + g.GLOBAL = g; + + g.eval("" + function f(obj) { + obj.foo; + assertEq(dbg.hasDebuggee(GLOBAL), true); + }); + + g.eval("(" + function test() { + f({ get foo() { dbg.addDebuggee(GLOBAL); } }); + } + ")();"); +})(); + +// Test setter. +(function testSetter() { + var g = newGlobal(); + g.dbg = new Debugger; + g.GLOBAL = g; + + g.eval("" + function f(obj) { + obj.foo = 42; + assertEq(dbg.hasDebuggee(GLOBAL), true); + }); + + g.eval("(" + function test() { + f({ set foo(v) { dbg.addDebuggee(GLOBAL); } }); + } + ")();"); +})(); + +// Test toString. +(function testToString() { + var g = newGlobal(); + g.dbg = new Debugger; + g.GLOBAL = g; + + g.eval("" + function f(obj) { + obj + ""; + assertEq(dbg.hasDebuggee(GLOBAL), true); + }); + + g.eval("(" + function test() { + f({ toString: function () { dbg.addDebuggee(GLOBAL); }}); + } + ")();"); +})(); + +// Test valueOf. +(function testValueOf() { + var g = newGlobal(); + g.dbg = new Debugger; + g.GLOBAL = g; + + g.eval("" + function f(obj) { + obj + ""; + assertEq(dbg.hasDebuggee(GLOBAL), true); + }); + + g.eval("(" + function test() { + f({ valueOf: function () { dbg.addDebuggee(GLOBAL); }}); + } + ")();"); +})(); + +// Test proxy trap. +(function testProxyTrap() { + var g = newGlobal(); + g.dbg = new Debugger; + g.GLOBAL = g; + + g.eval("" + function f(proxy) { + proxy["foo"]; + assertEq(dbg.hasDebuggee(GLOBAL), true); + }); + + g.eval("(" + function test() { + var handler = { get: function () { dbg.addDebuggee(GLOBAL); } }; + var proxy = new Proxy({}, handler); + f(proxy); + } + ")();"); +})(); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-24.js b/js/src/jit-test/tests/debug/Debugger-debuggees-24.js new file mode 100644 index 000000000..cf554c16a --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-24.js @@ -0,0 +1,55 @@ +// Turning debugger on for a particular global with on-stack scripts shouldn't +// make other globals' scripts observable. + +var g1 = newGlobal(); +var g2 = newGlobal(); +var g3 = newGlobal(); + +g1.eval("" + function f() { + var name = "f"; + g(); + return name; +}); +g2.eval("" + function g() { + var name = "g"; + h(); + return name; +}); +g3.eval("" + function h() { + var name = "h"; + toggle(); + return name; +}); + +g1.g = g2.g; +g2.h = g3.h; + +function name(f) { + return f.environment.getVariable("name"); +} + +var dbg = new Debugger; +g3.toggle = function () { + var frame; + + // Only f should be visible. + dbg.addDebuggee(g1); + frame = dbg.getNewestFrame(); + assertEq(name(frame), "f"); + + // Now h should also be visible. + dbg.addDebuggee(g3); + frame = dbg.getNewestFrame(); + assertEq(name(frame), "h"); + assertEq(name(frame.older), "f"); + + // Finally everything should be visible. + dbg.addDebuggee(g2); + frame = dbg.getNewestFrame(); + assertEq(name(frame), "h"); + assertEq(name(frame.older), "g"); + assertEq(name(frame.older.older), "f"); +}; + +g1.eval("(" + function () { f(); } + ")();"); + diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-25.js b/js/src/jit-test/tests/debug/Debugger-debuggees-25.js new file mode 100644 index 000000000..4ded8e152 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-25.js @@ -0,0 +1,48 @@ +// Turning debugger off global at a time. + +var g1 = newGlobal(); +var g2 = newGlobal(); +var g3 = newGlobal(); + +g1.eval("" + function f() { + var name = "f"; + g(); + return name; +}); +g2.eval("" + function g() { + var name = "g"; + h(); + return name; +}); +g3.eval("" + function h() { + var name = "h"; + toggle(); + return name; +}); + +g1.g = g2.g; +g2.h = g3.h; + +function name(f) { + return f.environment.getVariable("name"); +} + +var dbg = new Debugger; +g3.toggle = function () { + var frame; + + // Add all globals. + dbg.addDebuggee(g1); + dbg.addDebuggee(g3); + dbg.addDebuggee(g2); + + // Remove one at a time. + dbg.removeDebuggee(g3); + assertEq(name(dbg.getNewestFrame()), "g"); + dbg.removeDebuggee(g2); + assertEq(name(dbg.getNewestFrame()), "f"); + dbg.removeDebuggee(g1); +}; + +g1.eval("(" + function () { f(); } + ")();"); + diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-26.js b/js/src/jit-test/tests/debug/Debugger-debuggees-26.js new file mode 100644 index 000000000..20072fc4b --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-26.js @@ -0,0 +1,34 @@ +// Ion can bail in-place when throwing exceptions with debug mode toggled on. + +load(libdir + "jitopts.js"); + +if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation)) + quit(); + +withJitOptions(Opts_Ion2NoOffthreadCompilation, function () { + var g = newGlobal(); + var dbg = new Debugger; + + g.toggle = function toggle(x, d) { + if (d) { + dbg.addDebuggee(g); + var frame = dbg.getNewestFrame().older; + assertEq(frame.callee.name, "f"); + assertEq(frame.implementation, "ion"); + throw 42; + } + }; + + g.eval("" + function f(x, d) { g(x, d); }); + g.eval("" + function g(x, d) { toggle(x, d); }); + + try { + g.eval("(" + function test() { + for (var i = 0; i < 5; i++) + f(42, false); + f(42, true); + } + ")();"); + } catch (exc) { + assertEq(exc, 42); + } +}); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-27.js b/js/src/jit-test/tests/debug/Debugger-debuggees-27.js new file mode 100644 index 000000000..05ff5e9e4 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-27.js @@ -0,0 +1,19 @@ +// Test that we can OSR with the same script on the stack multiple times. + +var g = newGlobal(); +var dbg = new Debugger; + +g.toggle = function toggle() { + dbg.addDebuggee(g); + var frame = dbg.getNewestFrame(); +} + +g.eval("" + function f(x) { + if (x == 0) { + toggle(); + return; + } + f(x - 1); +}); + +g.eval("f(3);"); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-28.js b/js/src/jit-test/tests/debug/Debugger-debuggees-28.js new file mode 100644 index 000000000..f79ce2b9d --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-28.js @@ -0,0 +1,109 @@ +// Test that on->off->on and off->on->off toggles don't crash. + +function addRemove(dbg, g) { + dbg.addDebuggee(g); + var f = dbg.getNewestFrame(); + while (f) + f = f.older; + dbg.removeDebuggee(g); +} + +function removeAdd(dbg, g) { + dbg.removeDebuggee(g); + dbg.addDebuggee(g); + var f = dbg.getNewestFrame(); + while (f) + f = f.older; +} + +function newGlobalDebuggerPair(toggleSeq) { + var g = newGlobal(); + var dbg = new Debugger; + + if (toggleSeq == removeAdd) + dbg.addDebuggee(g); + + g.eval("" + function f() { return g(); }); + g.eval("" + function g() { return h(); }); + g.eval("line0 = Error().lineNumber;"); + g.eval("" + function h() { + for (var i = 0; i < 100; i++) + interruptIf(i == 95); + debugger; + return i; + }); + + setInterruptCallback(function () { return true; }); + + return [g, dbg]; +} + +function testInterrupt(toggleSeq) { + var [g, dbg] = newGlobalDebuggerPair(toggleSeq); + + setInterruptCallback(function () { + toggleSeq(dbg, g); + return true; + }); + + assertEq(g.f(), 100); +} + +function testPrologue(toggleSeq) { + var [g, dbg] = newGlobalDebuggerPair(toggleSeq); + + dbg.onEnterFrame = function (f) { + if (f.callee && f.callee.name == "h") + toggleSeq(dbg, g); + }; + + assertEq(g.f(), 100); +} + +function testEpilogue(toggleSeq) { + var [g, dbg] = newGlobalDebuggerPair(toggleSeq); + + dbg.onEnterFrame = function (f) { + if (f.callee && f.callee.name == "h") { + f.onPop = function () { + toggleSeq(dbg, g); + }; + } + }; + + assertEq(g.f(), 100); +} + +function testTrap(toggleSeq) { + var [g, dbg] = newGlobalDebuggerPair(toggleSeq); + + dbg.onEnterFrame = function (f) { + if (f.callee && f.callee.name == "h") { + var offs = f.script.getLineOffsets(g.line0 + 2); + assertEq(offs.length > 0, true); + f.script.setBreakpoint(offs[0], { hit: function () { + toggleSeq(dbg, g); + }}); + } + }; + + assertEq(g.f(), 100); +} + +function testDebugger(toggleSeq) { + var [g, dbg] = newGlobalDebuggerPair(toggleSeq); + + dbg.onDebuggerStatement = function () { + toggleSeq(dbg, g); + }; + + assertEq(g.f(), 100); +} + +testInterrupt(addRemove); +testInterrupt(removeAdd); + +testPrologue(removeAdd); +testEpilogue(removeAdd); +testTrap(removeAdd); +testDebugger(removeAdd); diff --git a/js/src/jit-test/tests/debug/Debugger-debuggees-29.js b/js/src/jit-test/tests/debug/Debugger-debuggees-29.js new file mode 100644 index 000000000..30e97701a --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-debuggees-29.js @@ -0,0 +1,6 @@ +// Debugger.prototype.addDebuggee should not accept invisible-to-debugger globals. +load(libdir + 'asserts.js'); + +var g = newGlobal({ invisibleToDebugger: true }); + +assertThrowsInstanceOf(() => { new Debugger(g); }, TypeError); diff --git a/js/src/jit-test/tests/debug/Debugger-enabled-01.js b/js/src/jit-test/tests/debug/Debugger-enabled-01.js new file mode 100644 index 000000000..3c4ab227f --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-enabled-01.js @@ -0,0 +1,18 @@ +var desc = Object.getOwnPropertyDescriptor(Debugger.prototype, "enabled"); +assertEq(typeof desc.get, 'function'); +assertEq(typeof desc.set, 'function'); + +var g = newGlobal(); +var hits; +var dbg = new Debugger(g); +assertEq(dbg.enabled, true); +dbg.onDebuggerStatement = function () { hits++; }; + +var vals = [true, false, null, undefined, NaN, "blah", {}]; +for (var i = 0; i < vals.length; i++) { + dbg.enabled = vals[i]; + assertEq(dbg.enabled, !!vals[i]); + hits = 0; + g.eval("debugger;"); + assertEq(hits, vals[i] ? 1 : 0); +} diff --git a/js/src/jit-test/tests/debug/Debugger-enabled-02.js b/js/src/jit-test/tests/debug/Debugger-enabled-02.js new file mode 100644 index 000000000..97a5eb3ed --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-enabled-02.js @@ -0,0 +1,17 @@ +// Tests that hooks work if set while the Debugger is disabled. + +var g = newGlobal(); +var dbg = new Debugger(g); +var log = ""; + +g.eval("" + function f() { return 42; }); + +dbg.enabled = false; +dbg.onEnterFrame = function (frame) { + log += "1"; +}; +dbg.enabled = true; + +g.f(); + +assertEq(log, "1"); diff --git a/js/src/jit-test/tests/debug/Debugger-findAllGlobals-01.js b/js/src/jit-test/tests/debug/Debugger-findAllGlobals-01.js new file mode 100644 index 000000000..7400ef9ea --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findAllGlobals-01.js @@ -0,0 +1,24 @@ +// Debugger.prototype.findAllGlobals surface. + +load(libdir + 'asserts.js'); + +var dbg = new Debugger; +var d = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(dbg), 'findAllGlobals'); +assertEq(d.configurable, true); +assertEq(d.enumerable, false); +assertEq(d.writable, true); +assertEq(typeof d.value, 'function'); +assertEq(dbg.findAllGlobals.length, 0); +assertEq(dbg.findAllGlobals.name, 'findAllGlobals'); + +// findAllGlobals can only be applied to real Debugger instances. +assertThrowsInstanceOf(function() { + Debugger.prototype.findAllGlobals.call(Debugger.prototype); + }, + TypeError); +var a = dbg.findAllGlobals(); +assertEq(a instanceof Array, true); +assertEq(a.length > 0, true); +for (g of a) { + assertEq(g instanceof Debugger.Object, true); +} diff --git a/js/src/jit-test/tests/debug/Debugger-findAllGlobals-02.js b/js/src/jit-test/tests/debug/Debugger-findAllGlobals-02.js new file mode 100644 index 000000000..d8766c160 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findAllGlobals-02.js @@ -0,0 +1,27 @@ +// Debugger.prototype.findAllGlobals finds ALL the globals! + +var g1 = newGlobal(); // Created before the Debugger; debuggee. +var g2 = newGlobal(); // Created before the Debugger; not debuggee. + +var dbg = new Debugger; + +var g3 = newGlobal(); // Created after the Debugger; debuggee. +var g4 = newGlobal(); // Created after the Debugger; not debuggee. + +var g1w = dbg.addDebuggee(g1); +var g3w = dbg.addDebuggee(g3); + +var a = dbg.findAllGlobals(); + +// Get Debugger.Objects viewing the globals from their own compartments; +// this is the sort that findAllGlobals and addDebuggee return. +var g2w = g1w.makeDebuggeeValue(g2).unwrap(); +var g4w = g1w.makeDebuggeeValue(g4).unwrap(); +var thisw = g1w.makeDebuggeeValue(this).unwrap(); + +// Check that they're all there. +assertEq(a.indexOf(g1w) != -1, true); +assertEq(a.indexOf(g2w) != -1, true); +assertEq(a.indexOf(g3w) != -1, true); +assertEq(a.indexOf(g4w) != -1, true); +assertEq(a.indexOf(thisw) != -1, true); diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-01.js b/js/src/jit-test/tests/debug/Debugger-findObjects-01.js new file mode 100644 index 000000000..4ed43310a --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findObjects-01.js @@ -0,0 +1,4 @@ +// In a debugger with no debuggees, findObjects should return no objects. + +var dbg = new Debugger; +assertEq(dbg.findObjects().length, 0); diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-02.js b/js/src/jit-test/tests/debug/Debugger-findObjects-02.js new file mode 100644 index 000000000..bfb33391f --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findObjects-02.js @@ -0,0 +1,18 @@ +// In a debuggee with live objects, findObjects finds those objects. + +var g = newGlobal(); + +let defObject = v => g.eval(`this.${v} = { toString: () => "[object ${v}]" }`); +defObject("a"); +defObject("b"); +defObject("c"); + +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); +var aw = gw.makeDebuggeeValue(g.a); +var bw = gw.makeDebuggeeValue(g.b); +var cw = gw.makeDebuggeeValue(g.c); + +assertEq(dbg.findObjects().indexOf(aw) != -1, true); +assertEq(dbg.findObjects().indexOf(bw) != -1, true); +assertEq(dbg.findObjects().indexOf(cw) != -1, true); diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-03.js b/js/src/jit-test/tests/debug/Debugger-findObjects-03.js new file mode 100644 index 000000000..d391415cc --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findObjects-03.js @@ -0,0 +1,12 @@ +// findObjects' result includes objects referenced by other objects. + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + +g.eval('this.a = { b: {} };'); + +var bw = gw.makeDebuggeeValue(g.a.b); + +var objects = dbg.findObjects(); +assertEq(objects.indexOf(bw) != -1, true); diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-04.js b/js/src/jit-test/tests/debug/Debugger-findObjects-04.js new file mode 100644 index 000000000..a01378e35 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findObjects-04.js @@ -0,0 +1,16 @@ +// findObjects' result includes objects captured by closures. + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + +g.eval(` + this.f = (function () { + let a = { foo: () => {} }; + return () => a; + }()); +`); + +let objects = dbg.findObjects(); +let aw = gw.makeDebuggeeValue(g.f()); +assertEq(objects.indexOf(aw) != -1, true); diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-05.js b/js/src/jit-test/tests/debug/Debugger-findObjects-05.js new file mode 100644 index 000000000..051209196 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findObjects-05.js @@ -0,0 +1,10 @@ +// findObjects' result doesn't include any duplicates. + +var g = newGlobal(); +var dbg = new Debugger(); +dbg.addDebuggee(g); + +let objects = dbg.findObjects(); +let set = new Set(objects); + +assertEq(objects.length, set.size); diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-06.js b/js/src/jit-test/tests/debug/Debugger-findObjects-06.js new file mode 100644 index 000000000..8bdedab03 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findObjects-06.js @@ -0,0 +1,14 @@ +// In a debugger with multiple debuggees, findObjects finds objects from all debuggees. + +var g1 = newGlobal(); +var g2 = newGlobal(); +var dbg = new Debugger(); +var g1w = dbg.addDebuggee(g1); +var g2w = dbg.addDebuggee(g2); + +g1.eval('this.a = {};'); +g2.eval('this.b = {};'); + +var objects = dbg.findObjects(); +assertEq(objects.indexOf(g1w.makeDebuggeeValue(g1.a)) != -1, true); +assertEq(objects.indexOf(g2w.makeDebuggeeValue(g2.b)) != -1, true); diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-07.js b/js/src/jit-test/tests/debug/Debugger-findObjects-07.js new file mode 100644 index 000000000..3e306a86c --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findObjects-07.js @@ -0,0 +1,22 @@ +// findObjects can filter objects by class name. + +var g = newGlobal(); + +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + +g.eval('this.re = /foo/;'); +g.eval('this.d = new Date();'); + +var rew = gw.makeDebuggeeValue(g.re); +var dw = gw.makeDebuggeeValue(g.d); + +var objects; + +objects = dbg.findObjects({ class: "RegExp" }); +assertEq(objects.indexOf(rew) != -1, true); +assertEq(objects.indexOf(dw) == -1, true); + +objects = dbg.findObjects({ class: "Date" }); +assertEq(objects.indexOf(dw) != -1, true); +assertEq(objects.indexOf(rew) == -1, true); diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-08.js b/js/src/jit-test/tests/debug/Debugger-findObjects-08.js new file mode 100644 index 000000000..fd6ceb103 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findObjects-08.js @@ -0,0 +1,12 @@ +// Passing bad query properties to Debugger.prototype.findScripts throws. + +load(libdir + 'asserts.js'); + +var dbg = new Debugger(); +var g = newGlobal(); + +assertThrowsInstanceOf(() => dbg.findObjects({ class: null }), TypeError); +assertThrowsInstanceOf(() => dbg.findObjects({ class: true }), TypeError); +assertThrowsInstanceOf(() => dbg.findObjects({ class: 1337 }), TypeError); +assertThrowsInstanceOf(() => dbg.findObjects({ class: /re/ }), TypeError); +assertThrowsInstanceOf(() => dbg.findObjects({ class: {} }), TypeError); diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-09.js b/js/src/jit-test/tests/debug/Debugger-findObjects-09.js new file mode 100644 index 000000000..a50f2be08 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findObjects-09.js @@ -0,0 +1,9 @@ +// We don't return objects where our query's class name is the prefix of the +// object's class name and vice versa. + +var dbg = new Debugger(); +var g = newGlobal(); +var gw = dbg.addDebuggee(g); + +assertEq(dbg.findObjects({ class: "Objec" }).length, 0); +assertEq(dbg.findObjects({ class: "Objectttttt" }).length, 0); diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-10.js b/js/src/jit-test/tests/debug/Debugger-findObjects-10.js new file mode 100644 index 000000000..9595f6963 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findObjects-10.js @@ -0,0 +1,5 @@ +// Debugger.prototype.findObjects should not expose internal JSFunction objects. + +var g = newGlobal(); +g.eval(`function f() { return function() {}; }`); +new Debugger(g).findObjects(); diff --git a/js/src/jit-test/tests/debug/Debugger-findObjects-11.js b/js/src/jit-test/tests/debug/Debugger-findObjects-11.js new file mode 100644 index 000000000..f0330447f --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findObjects-11.js @@ -0,0 +1,7 @@ +// This shouldn't segfault. + +var g = newGlobal(); +g.eval(`function f() { return function() { + function g() {} +}; }`); +new Debugger(g).findObjects(); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-01.js b/js/src/jit-test/tests/debug/Debugger-findScripts-01.js new file mode 100644 index 000000000..e39691605 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-01.js @@ -0,0 +1,4 @@ +// In a debugger with no debuggees, findScripts should return no scripts. + +var dbg = new Debugger; +assertEq(dbg.findScripts().length, 0); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-02.js b/js/src/jit-test/tests/debug/Debugger-findScripts-02.js new file mode 100644 index 000000000..a9d85b04e --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-02.js @@ -0,0 +1,16 @@ +// In a debuggee with functions, findScripts finds those functions' scripts. + +var g = newGlobal(); +g.eval('function f(){}'); +g.eval('function g(){}'); +g.eval('function h(){}'); + +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); +var fw = gw.makeDebuggeeValue(g.f); +var gw = gw.makeDebuggeeValue(g.g); +var hw = gw.makeDebuggeeValue(g.h); + +assertEq(dbg.findScripts().indexOf(fw.script) != -1, true); +assertEq(dbg.findScripts().indexOf(gw.script) != -1, true); +assertEq(dbg.findScripts().indexOf(hw.script) != -1, true); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-03.js b/js/src/jit-test/tests/debug/Debugger-findScripts-03.js new file mode 100644 index 000000000..1affdb254 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-03.js @@ -0,0 +1,16 @@ +// While eval code is running, findScripts returns its script. + +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +g.check = function () { + log += 'c'; + var frame = dbg.getNewestFrame(); + assertEq(frame.type, "eval"); + assertEq(dbg.findScripts().indexOf(frame.script) != -1, true); +}; + +log = ''; +g.eval('check()'); +assertEq(log, 'c'); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-04.js b/js/src/jit-test/tests/debug/Debugger-findScripts-04.js new file mode 100644 index 000000000..3c2afe1e3 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-04.js @@ -0,0 +1,27 @@ +// Within a series of evals and calls, all their frames' scripts appear in findScripts' result. +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +g.check = function () { + log += 'c'; + var scripts = dbg.findScripts(); + + var innerEvalFrame = dbg.getNewestFrame(); + assertEq(innerEvalFrame.type, "eval"); + assertEq(scripts.indexOf(innerEvalFrame.script) != -1, true); + + var callFrame = innerEvalFrame.older; + assertEq(callFrame.type, "call"); + assertEq(scripts.indexOf(callFrame.script) != -1, true); + + var outerEvalFrame = callFrame.older; + assertEq(outerEvalFrame.type, "eval"); + assertEq(scripts.indexOf(outerEvalFrame.script) != -1, true); + assertEq(innerEvalFrame != outerEvalFrame, true); +}; + +g.eval('function f() { eval("check();") }'); +log = ''; +g.eval('f();'); +assertEq(log, 'c'); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-05.js b/js/src/jit-test/tests/debug/Debugger-findScripts-05.js new file mode 100644 index 000000000..a013408a1 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-05.js @@ -0,0 +1,18 @@ +// findScripts' result includes scripts for nested functions. +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); +var log; + +g.eval('function f() { return function g() { return function h() { return "Squee!"; } } }'); +var fw = gw.makeDebuggeeValue(g.f); +var gw = gw.makeDebuggeeValue(g.f()); +var hw = gw.makeDebuggeeValue(g.f()()); + +assertEq(fw.script != gw.script, true); +assertEq(fw.script != hw.script, true); + +var scripts = dbg.findScripts(); +assertEq(scripts.indexOf(fw.script) != -1, true); +assertEq(scripts.indexOf(gw.script) != -1, true); +assertEq(scripts.indexOf(hw.script) != -1, true); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-06.js b/js/src/jit-test/tests/debug/Debugger-findScripts-06.js new file mode 100644 index 000000000..0b49ce324 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-06.js @@ -0,0 +1,13 @@ +// In a debugger with multiple debuggees, findScripts finds scripts across all debuggees. +var g1 = newGlobal(); +var g2 = newGlobal(); +var dbg = new Debugger(); +var g1w = dbg.addDebuggee(g1); +var g2w = dbg.addDebuggee(g2); + +g1.eval('function f() {}'); +g2.eval('function g() {}'); + +var scripts = dbg.findScripts(); +assertEq(scripts.indexOf(g1w.makeDebuggeeValue(g1.f).script) != -1, true); +assertEq(scripts.indexOf(g2w.makeDebuggeeValue(g2.g).script) != -1, true); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-07.js b/js/src/jit-test/tests/debug/Debugger-findScripts-07.js new file mode 100644 index 000000000..73513a545 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-07.js @@ -0,0 +1,33 @@ +// findScripts can filter scripts by global. +var g1 = newGlobal(); +var g2 = newGlobal(); +var g3 = newGlobal(); + +var dbg = new Debugger(); +var g1w = dbg.addDebuggee(g1); +var g2w = dbg.addDebuggee(g2); + +g1.eval('function f() {}'); +g2.eval('function g() {}'); +g2.eval('function h() {}'); +var g1fw = g1w.makeDebuggeeValue(g1.f); +var g2gw = g2w.makeDebuggeeValue(g2.g); + +var scripts; + +scripts = dbg.findScripts({}); +assertEq(scripts.indexOf(g1fw.script) != -1, true); +assertEq(scripts.indexOf(g2gw.script) != -1, true); + +scripts = dbg.findScripts({global: g1}); +assertEq(scripts.indexOf(g1fw.script) != -1, true); +assertEq(scripts.indexOf(g2gw.script) != -1, false); + +scripts = dbg.findScripts({global: g2}); +assertEq(scripts.indexOf(g1fw.script) != -1, false); +assertEq(scripts.indexOf(g2gw.script) != -1, true); + +scripts = dbg.findScripts({global: g3}); +// findScripts should only return debuggee scripts, and g3 isn't a +// debuggee, so this should be completely empty. +assertEq(scripts.length, 0); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-08-script2 b/js/src/jit-test/tests/debug/Debugger-findScripts-08-script2 new file mode 100644 index 000000000..40b3aafff --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-08-script2 @@ -0,0 +1,3 @@ +// -*- mode: js2 -*- +g1.eval('function g1g() {}'); +g2.eval('function g2g() {}'); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-08.js b/js/src/jit-test/tests/debug/Debugger-findScripts-08.js new file mode 100644 index 000000000..c43682fad --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-08.js @@ -0,0 +1,81 @@ +// Debugger.prototype.findScripts can filter scripts by URL. +var g1 = newGlobal(); +var g2 = newGlobal(); +var g3 = newGlobal(); + +// Define some functions whose url will be this test file. +g1.eval('function g1f() {}'); +g2.eval('function g2f() {}'); + +// Define some functions whose url will be a different file. +url2 = scriptdir + "Debugger-findScripts-08-script2"; +load(url2); + +var dbg = new Debugger(); +var g1w = dbg.addDebuggee(g1); +var g2w = dbg.addDebuggee(g2); +var g3w = dbg.addDebuggee(g3); + +var g1fw = g1w.makeDebuggeeValue(g1.g1f); +var g1gw = g1w.makeDebuggeeValue(g1.g1g); +var g2fw = g2w.makeDebuggeeValue(g2.g2f); +var g2gw = g2w.makeDebuggeeValue(g2.g2g); + +// Find the url of this file. +url = g1fw.script.url; + +var scripts; + +scripts = dbg.findScripts({}); +assertEq(scripts.indexOf(g1fw.script) != -1, true); +assertEq(scripts.indexOf(g1gw.script) != -1, true); +assertEq(scripts.indexOf(g2fw.script) != -1, true); +assertEq(scripts.indexOf(g2gw.script) != -1, true); + +scripts = dbg.findScripts({url:url}); +assertEq(scripts.indexOf(g1fw.script) != -1, true); +assertEq(scripts.indexOf(g1gw.script) != -1, false); +assertEq(scripts.indexOf(g2fw.script) != -1, true); +assertEq(scripts.indexOf(g2gw.script) != -1, false); + +scripts = dbg.findScripts({url:url2}); +assertEq(scripts.indexOf(g1fw.script) != -1, false); +assertEq(scripts.indexOf(g1gw.script) != -1, true); +assertEq(scripts.indexOf(g2fw.script) != -1, false); +assertEq(scripts.indexOf(g2gw.script) != -1, true); + +scripts = dbg.findScripts({url:url, global:g1}); +assertEq(scripts.indexOf(g1fw.script) != -1, true); +assertEq(scripts.indexOf(g1gw.script) != -1, false); +assertEq(scripts.indexOf(g2fw.script) != -1, false); +assertEq(scripts.indexOf(g2gw.script) != -1, false); + +scripts = dbg.findScripts({url:url2, global:g1}); +assertEq(scripts.indexOf(g1fw.script) != -1, false); +assertEq(scripts.indexOf(g1gw.script) != -1, true); +assertEq(scripts.indexOf(g2fw.script) != -1, false); +assertEq(scripts.indexOf(g2gw.script) != -1, false); + +scripts = dbg.findScripts({url:url, global:g2}); +assertEq(scripts.indexOf(g1fw.script) != -1, false); +assertEq(scripts.indexOf(g1gw.script) != -1, false); +assertEq(scripts.indexOf(g2fw.script) != -1, true); +assertEq(scripts.indexOf(g2gw.script) != -1, false); + +scripts = dbg.findScripts({url:url2, global:g2}); +assertEq(scripts.indexOf(g1fw.script) != -1, false); +assertEq(scripts.indexOf(g1gw.script) != -1, false); +assertEq(scripts.indexOf(g2fw.script) != -1, false); +assertEq(scripts.indexOf(g2gw.script) != -1, true); + +scripts = dbg.findScripts({url:"xlerb"}); // "XLERB"??? +assertEq(scripts.indexOf(g1fw.script) != -1, false); +assertEq(scripts.indexOf(g1gw.script) != -1, false); +assertEq(scripts.indexOf(g2fw.script) != -1, false); +assertEq(scripts.indexOf(g2gw.script) != -1, false); + +scripts = dbg.findScripts({url:url, global:g3}); +assertEq(scripts.indexOf(g1fw.script) != -1, false); +assertEq(scripts.indexOf(g1gw.script) != -1, false); +assertEq(scripts.indexOf(g2fw.script) != -1, false); +assertEq(scripts.indexOf(g2gw.script) != -1, false); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-09.js b/js/src/jit-test/tests/debug/Debugger-findScripts-09.js new file mode 100644 index 000000000..468f33205 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-09.js @@ -0,0 +1,45 @@ +// Passing bad query properties to Debugger.prototype.findScripts throws. +load(libdir + 'asserts.js'); + +var dbg = new Debugger(); +var g = newGlobal(); +assertEq(dbg.findScripts().length, 0); +assertEq(dbg.findScripts({}).length, 0); + +assertEq(dbg.findScripts({global:g}).length, 0); +assertThrowsInstanceOf(function () { dbg.findScripts({global:null}); }, TypeError); +assertThrowsInstanceOf(function () { dbg.findScripts({global:true}); }, TypeError); +assertThrowsInstanceOf(function () { dbg.findScripts({global:4}); }, TypeError); +assertThrowsInstanceOf(function () { dbg.findScripts({global:"I must have fruit!"}); }, TypeError); + +assertEq(dbg.findScripts({url:""}).length, 0); +assertThrowsInstanceOf(function () { dbg.findScripts({url:null}); }, TypeError); +assertThrowsInstanceOf(function () { dbg.findScripts({url:true}); }, TypeError); +assertThrowsInstanceOf(function () { dbg.findScripts({url:4}); }, TypeError); +assertThrowsInstanceOf(function () { dbg.findScripts({url:{}}); }, TypeError); + +assertEq(dbg.findScripts({url:"", line:1}).length, 0); +assertEq(dbg.findScripts({url:"", line:Math.sqrt(4)}).length, 0); + +// A 'line' property without a 'url' property is verboten. +assertThrowsInstanceOf(function () { dbg.findScripts({line:1}); }, TypeError); + +assertThrowsInstanceOf(function () { dbg.findScripts({url:"",line:null}); }, TypeError); +assertThrowsInstanceOf(function () { dbg.findScripts({url:"",line:{}}); }, TypeError); +assertThrowsInstanceOf(function () { dbg.findScripts({url:"",line:true}); }, TypeError); +assertThrowsInstanceOf(function () { dbg.findScripts({url:"",line:""}); }, TypeError); +assertThrowsInstanceOf(function () { dbg.findScripts({url:"",line:0}); }, TypeError); +assertThrowsInstanceOf(function () { dbg.findScripts({url:"",line:-1}); }, TypeError); +assertThrowsInstanceOf(function () { dbg.findScripts({url:"",line:1.5}); }, TypeError); + +// Values of any type for 'innermost' are accepted. +assertEq(dbg.findScripts({url:"", line:1, innermost:true}).length, 0); +assertEq(dbg.findScripts({url:"", line:1, innermost:1}).length, 0); +assertEq(dbg.findScripts({url:"", line:1, innermost:"yes"}).length, 0); +assertEq(dbg.findScripts({url:"", line:1, innermost:{}}).length, 0); +assertEq(dbg.findScripts({url:"", line:1, innermost:[]}).length, 0); + +// An 'innermost' property without 'url' and 'line' properties is verboten. +assertThrowsInstanceOf(function () { dbg.findScripts({innermost:true}); }, TypeError); +assertThrowsInstanceOf(function () { dbg.findScripts({innermost:true, line:1}); }, TypeError); +assertThrowsInstanceOf(function () { dbg.findScripts({innermost:true, url:"foo"}); }, TypeError); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-10.js b/js/src/jit-test/tests/debug/Debugger-findScripts-10.js new file mode 100644 index 000000000..6b8e372d2 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-10.js @@ -0,0 +1,13 @@ +// Specifying a non-debuggee global in a Debugger.prototype.findScripts query should +// cause the query to return no scripts. + +var g1 = newGlobal(); +g1.eval('function f(){}'); + +var g2 = newGlobal(); +g2.eval('function g(){}'); + +var dbg = new Debugger(g1); +assertEq(dbg.findScripts({global:g1}).length > 0, true); +assertEq(dbg.findScripts({global:g2}).length, 0); +assertEq(dbg.findScripts({global:this}).length, 0); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-11-script2 b/js/src/jit-test/tests/debug/Debugger-findScripts-11-script2 new file mode 100644 index 000000000..7a170645e --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-11-script2 @@ -0,0 +1,18 @@ +// -*- mode: js2 -*- +// Line numbers in this file are checked in Debugger-findScripts-11.js. + +// line 3 + +var x = ""; +function f() { + x += "the map"; // line 8 + return function g() { + return "to me what you have stolen"; // line 10 + }; +} + +function h(x, y) { + if (x == 0) return y+1; // line 15 + if (y == 0) return h(x-1, 1); + return h(x-1, h(x, y-1)); +} diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-11.js b/js/src/jit-test/tests/debug/Debugger-findScripts-11.js new file mode 100644 index 000000000..670b3d742 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-11.js @@ -0,0 +1,36 @@ +// Debugger.prototype.findScripts can filter scripts by line number. +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + +var scriptname = scriptdir + 'Debugger-findScripts-11-script2'; +g.load(scriptname); + +var gfw = gw.makeDebuggeeValue(g.f); +var ggw = gw.makeDebuggeeValue(g.f()); +var ghw = gw.makeDebuggeeValue(g.h); + +// Specifying a line outside of all functions screens out all function scripts. +assertEq(dbg.findScripts({url:scriptname, line:3}).indexOf(gfw.script) != -1, false); +assertEq(dbg.findScripts({url:scriptname, line:3}).indexOf(ggw.script) != -1, false); +assertEq(dbg.findScripts({url:scriptname, line:3}).indexOf(ghw.script) != -1, false); + +// Specifying a different url screens out scripts, even when global and line match. +assertEq(dbg.findScripts({url:"xlerb", line:8}).indexOf(gfw.script) != -1, false); +assertEq(dbg.findScripts({url:"xlerb", line:8}).indexOf(ggw.script) != -1, false); +assertEq(dbg.findScripts({url:"xlerb", line:8}).indexOf(ghw.script) != -1, false); + +// A line number within a function selects that function's script. +assertEq(dbg.findScripts({url:scriptname, line:8}).indexOf(gfw.script) != -1, true); +assertEq(dbg.findScripts({url:scriptname, line:8}).indexOf(ggw.script) != -1, false); +assertEq(dbg.findScripts({url:scriptname, line:8}).indexOf(ghw.script) != -1, false); + +// A line number within a nested function selects all enclosing functions' scripts. +assertEq(dbg.findScripts({url:scriptname, line:10}).indexOf(gfw.script) != -1, true); +assertEq(dbg.findScripts({url:scriptname, line:10}).indexOf(ggw.script) != -1, true); +assertEq(dbg.findScripts({url:scriptname, line:10}).indexOf(ghw.script) != -1, false); + +// A line number in a non-nested function selects that function. +assertEq(dbg.findScripts({url:scriptname, line:15}).indexOf(gfw.script) != -1, false); +assertEq(dbg.findScripts({url:scriptname, line:15}).indexOf(ggw.script) != -1, false); +assertEq(dbg.findScripts({url:scriptname, line:15}).indexOf(ghw.script) != -1, true); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-12-script1 b/js/src/jit-test/tests/debug/Debugger-findScripts-12-script1 new file mode 100644 index 000000000..f9f484e97 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-12-script1 @@ -0,0 +1,19 @@ +// -*- mode: js2 -*- +// Script for Debugger-findScripts-12.js to load. +// Line numbers in this script are cited in the test. + +function f() { + // line 6 + function ff() { + return "my wuv, I want you always beside me"; // line 8 + }; + ff.global = this; + return ff; +}; + +function g() { + return "to Oz"; // line 15 +} + +f.global = this; +g.global = this; diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-12-script2 b/js/src/jit-test/tests/debug/Debugger-findScripts-12-script2 new file mode 100644 index 000000000..3350c8ed2 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-12-script2 @@ -0,0 +1,19 @@ +// -*- mode: js2 -*- +// Script for Debugger-findScripts-12.js to load. +// Line numbers in this script are cited in the test, and must align with ...-script1. + +function h() { + // line 6 + function hh() { + return "on investment"; // line 8 + }; + hh.global = this; + return hh; +}; + +function i() { + return "to innocence"; // line 15 +} + +h.global = this; +i.global = this; diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-12.js b/js/src/jit-test/tests/debug/Debugger-findScripts-12.js new file mode 100644 index 000000000..e3adee206 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-12.js @@ -0,0 +1,128 @@ +// Debugger.prototype.findScripts can filter by global, url, and line number. + +// Two scripts, with different functions at the same line numbers. +var url1 = scriptdir + 'Debugger-findScripts-12-script1'; +var url2 = scriptdir + 'Debugger-findScripts-12-script2'; + +// Three globals: two with code, one with nothing. +var g1 = newGlobal(); +g1.toSource = () => "[global g1]"; +g1.load(url1); +g1.load(url2); +var g2 = newGlobal(); +g2.toSource = () => "[global g2]"; +g2.load(url1); +g2.load(url2); +var g3 = newGlobal(); + +var dbg = new Debugger(g1, g2, g3); + +function script(func) { + var gw = dbg.addDebuggee(func.global); + var script = gw.makeDebuggeeValue(func).script; + script.toString = function () + "[Debugger.Script for " + func.name + " in " + uneval(func.global) + "]"; + return script; +} + +// The function scripts we know of. There may be random eval scripts involved, but +// we don't care about those. +var allScripts = ([g1.f, g1.f(), g1.g, g1.h, g1.h(), g1.i, + g2.f, g2.f(), g2.g, g2.h, g2.h(), g2.i].map(script)); + +// Search for scripts using |query|, expecting no members of allScripts +// except those given in |expected| in the result. If |expected| is +// omitted, expect no members of allScripts at all. +function queryExpectOnly(query, expected) { + print(); + print("queryExpectOnly(" + uneval(query) + ")"); + var scripts = dbg.findScripts(query); + var present = allScripts.filter(function (s) { return scripts.indexOf(s) != -1; }); + if (expected) { + expected = expected.map(script); + expected.forEach(function (s) { + if (present.indexOf(s) == -1) + assertEq(s + " not present", "is present"); + }); + present.forEach(function (s) { + if (expected.indexOf(s) == -1) + assertEq(s + " is present", "not present"); + }); + } else { + assertEq(present.length, 0); + } +} + +// We have twelve functions: two globals, each with two urls, each +// defining three functions. Show that all the different combinations of +// query parameters select what they should. + +// There are gaps in the pattern: +// - You can only filter by line if you're also filtering by url. +// - You can't ask for only the innermost scripts unless you're filtering by line. + +// Filtering by global, url, and line produces one function, or two +// where they are nested. +queryExpectOnly({ global:g1, url:url1, line: 6 }, [g1.f ]); +queryExpectOnly({ global:g1, url:url1, line: 8 }, [g1.f, g1.f()]); +queryExpectOnly({ global:g1, url:url1, line: 15 }, [g1.g ]); +queryExpectOnly({ global:g1, url:url2, line: 6 }, [g1.h ]); +queryExpectOnly({ global:g1, url:url2, line: 8 }, [g1.h, g1.h()]); +queryExpectOnly({ global:g1, url:url2, line: 15 }, [g1.i ]); +queryExpectOnly({ global:g2, url:url1, line: 6 }, [g2.f ]); +queryExpectOnly({ global:g2, url:url1, line: 8 }, [g2.f, g2.f()]); +queryExpectOnly({ global:g2, url:url1, line: 15 }, [g2.g ]); +queryExpectOnly({ global:g2, url:url2, line: 6 }, [g2.h ]); +queryExpectOnly({ global:g2, url:url2, line: 8 }, [g2.h, g2.h()]); +queryExpectOnly({ global:g2, url:url2, line: 15 }, [g2.i ]); + +// Filtering by global, url, and line, and requesting only the innermost +// function at each point, should produce only one function. +queryExpectOnly({ global:g1, url:url1, line: 6, innermost: true }, [g1.f ]); +queryExpectOnly({ global:g1, url:url1, line: 8, innermost: true }, [g1.f()]); +queryExpectOnly({ global:g1, url:url1, line: 15, innermost: true }, [g1.g ]); +queryExpectOnly({ global:g1, url:url2, line: 6, innermost: true }, [g1.h ]); +queryExpectOnly({ global:g1, url:url2, line: 8, innermost: true }, [g1.h()]); +queryExpectOnly({ global:g1, url:url2, line: 15, innermost: true }, [g1.i ]); +queryExpectOnly({ global:g2, url:url1, line: 6, innermost: true }, [g2.f ]); +queryExpectOnly({ global:g2, url:url1, line: 8, innermost: true }, [g2.f()]); +queryExpectOnly({ global:g2, url:url1, line: 15, innermost: true }, [g2.g ]); +queryExpectOnly({ global:g2, url:url2, line: 6, innermost: true }, [g2.h ]); +queryExpectOnly({ global:g2, url:url2, line: 8, innermost: true }, [g2.h()]); +queryExpectOnly({ global:g2, url:url2, line: 15, innermost: true }, [g2.i ]); + +// Filtering by url and global should produce sets of three scripts. +queryExpectOnly({ global:g1, url:url1 }, [g1.f, g1.f(), g1.g]); +queryExpectOnly({ global:g1, url:url2 }, [g1.h, g1.h(), g1.i]); +queryExpectOnly({ global:g2, url:url1 }, [g2.f, g2.f(), g2.g]); +queryExpectOnly({ global:g2, url:url2 }, [g2.h, g2.h(), g2.i]); + +// Filtering by url and line, innermost-only, should produce sets of two scripts, +// or four where there are nested functions. +queryExpectOnly({ url:url1, line: 6 }, [g1.f, g2.f ]); +queryExpectOnly({ url:url1, line: 8 }, [g1.f, g1.f(), g2.f, g2.f()]); +queryExpectOnly({ url:url1, line:15 }, [g1.g, g2.g ]); +queryExpectOnly({ url:url2, line: 6 }, [g1.h, g2.h ]); +queryExpectOnly({ url:url2, line: 8 }, [g1.h, g1.h(), g2.h, g2.h()]); +queryExpectOnly({ url:url2, line:15 }, [g1.i, g2.i ]); + +// Filtering by url and line, and requesting only the innermost scripts, +// should always produce pairs of scripts. +queryExpectOnly({ url:url1, line: 6, innermost: true }, [g1.f, g2.f ]); +queryExpectOnly({ url:url1, line: 8, innermost: true }, [g1.f(), g2.f()]); +queryExpectOnly({ url:url1, line:15, innermost: true }, [g1.g, g2.g ]); +queryExpectOnly({ url:url2, line: 6, innermost: true }, [g1.h, g2.h ]); +queryExpectOnly({ url:url2, line: 8, innermost: true }, [g1.h(), g2.h()]); +queryExpectOnly({ url:url2, line:15, innermost: true }, [g1.i, g2.i ]); + +// Filtering by global only should produce sets of six scripts. +queryExpectOnly({ global:g1 }, [g1.f, g1.f(), g1.g, g1.h, g1.h(), g1.i]); +queryExpectOnly({ global:g2 }, [g2.f, g2.f(), g2.g, g2.h, g2.h(), g2.i]); + +// Filtering by url should produce sets of six scripts. +queryExpectOnly({ url:url1 }, [g1.f, g1.f(), g1.g, g2.f, g2.f(), g2.g]); +queryExpectOnly({ url:url2 }, [g1.h, g1.h(), g1.i, g2.h, g2.h(), g2.i]); + +// Filtering by no axes should produce all twelve scripts. +queryExpectOnly({}, [g1.f, g1.f(), g1.g, g1.h, g1.h(), g1.i, + g2.f, g2.f(), g2.g, g2.h, g2.h(), g2.i]); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-14.js b/js/src/jit-test/tests/debug/Debugger-findScripts-14.js new file mode 100644 index 000000000..308344e79 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-14.js @@ -0,0 +1,30 @@ +// Debugger.prototype.findScripts can find the innermost script at a given +// source location. +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + +function script(f) { + return gw.makeDebuggeeValue(f).script; +} + +function arrayIsOnly(array, element) { + return array.length == 1 && array[0] === element; +} + +url = scriptdir + 'Debugger-findScripts-14.script1'; +g.load(url); + +var scripts; + +// When we're doing 'innermost' queries, we don't have to worry about finding +// random eval scripts: we should get exactly one script, for the function +// covering that line. +scripts = dbg.findScripts({url:url, line:4, innermost:true}); +assertEq(arrayIsOnly(scripts, script(g.f)), true); + +scripts = dbg.findScripts({url:url, line:6, innermost:true}); +assertEq(arrayIsOnly(scripts, script(g.f())), true); + +scripts = dbg.findScripts({url:url, line:8, innermost:true}); +assertEq(arrayIsOnly(scripts, script(g.f()())), true); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-14.script1 b/js/src/jit-test/tests/debug/Debugger-findScripts-14.script1 new file mode 100644 index 000000000..00da459fc --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-14.script1 @@ -0,0 +1,12 @@ +// -*- mode:js2 -*- + +function f() { + var x = 1; // line 4 + return function g() { + var y = 2; // line 6 + return function h() { + var z = 3; // line 8 + return x+y+z; + }; + }; +} diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-15.js b/js/src/jit-test/tests/debug/Debugger-findScripts-15.js new file mode 100644 index 000000000..c2308c613 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-15.js @@ -0,0 +1,9 @@ +// findScripts finds non-compile-and-go scripts + +var g = newGlobal(); +g.evaluate("function f(x) { return x + 1; }"); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var s = dbg.findScripts(); +var fw = gw.getOwnPropertyDescriptor("f").value; +assertEq(s.indexOf(fw.script) !== -1, true); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-16.js b/js/src/jit-test/tests/debug/Debugger-findScripts-16.js new file mode 100644 index 000000000..92b8176cc --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-16.js @@ -0,0 +1,12 @@ +// Bug 744731 - findScripts() finds active debugger executeInGlobal scripts. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + hits++; + assertEq(dbg.findScripts().indexOf(dbg.getNewestFrame().script) !== -1, true); +}; +gw.executeInGlobal("debugger;"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-17.js b/js/src/jit-test/tests/debug/Debugger-findScripts-17.js new file mode 100644 index 000000000..e837301ca --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-17.js @@ -0,0 +1,15 @@ +// Bug 744731 - findScripts() finds active debugger frame.eval scripts. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + dbg.onDebuggerStatement = function (frame) { + hits++; + assertEq(dbg.findScripts().indexOf(dbg.getNewestFrame().script) !== -1, true); + }; + frame.eval("debugger;"); +}; +g.eval("debugger;"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-18.js b/js/src/jit-test/tests/debug/Debugger-findScripts-18.js new file mode 100644 index 000000000..b7b250f2b --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-18.js @@ -0,0 +1,46 @@ +// In a debuggee with multiple scripts with varying displayURLs (aka //# +// sourceURL), findScripts can filter by displayURL. + +var g = newGlobal(); + +g.eval("function f(){} //# sourceURL=f.js"); +g.eval("function g(){} //# sourceURL=g.js"); +g.eval("function h(){}"); + +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); +var fw = gw.makeDebuggeeValue(g.f); +var ggw = gw.makeDebuggeeValue(g.g); +var hw = gw.makeDebuggeeValue(g.h); + +var fScripts = dbg.findScripts({ displayURL: "f.js" }); +assertEq(fScripts.indexOf(fw.script) != -1, true); +assertEq(fScripts.indexOf(ggw.script), -1); +assertEq(fScripts.indexOf(hw.script), -1); + + +fScripts = dbg.findScripts({ displayURL: "f.js", + line: 1 }); +assertEq(fScripts.indexOf(fw.script) != -1, true); +assertEq(fScripts.indexOf(ggw.script), -1); +assertEq(fScripts.indexOf(hw.script), -1); + +var gScripts = dbg.findScripts({ displayURL: "g.js" }); +assertEq(gScripts.indexOf(ggw.script) != -1, true); +assertEq(gScripts.indexOf(fw.script), -1); +assertEq(gScripts.indexOf(hw.script), -1); + +var allScripts = dbg.findScripts(); +assertEq(allScripts.indexOf(fw.script) != -1, true); +assertEq(allScripts.indexOf(ggw.script) != -1, true); +assertEq(allScripts.indexOf(hw.script) != -1, true); + +try { + dbg.findScripts({ displayURL: 3 }); + // Should never get here because the above line should throw + // JSMSG_UNEXPECTED_TYPE. + assertEq(true, false); +} catch(e) { + assertEq(e.name, "TypeError"); + assertEq(e.message.includes("displayURL"), true); +} diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-19.js b/js/src/jit-test/tests/debug/Debugger-findScripts-19.js new file mode 100644 index 000000000..0983c0eca --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-19.js @@ -0,0 +1,5 @@ +var g = newGlobal(); +var dbg = new Debugger(g); +try { g.eval('function drag(ev) {'); } catch (ex) { } +for (s of dbg.findScripts()) + s.lineCount; diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-20.js b/js/src/jit-test/tests/debug/Debugger-findScripts-20.js new file mode 100644 index 000000000..c561bf910 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-20.js @@ -0,0 +1,20 @@ +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + +g.eval('function f(){}'); + +var o = gw.makeDebuggeeValue(g.f); + +var allScripts = dbg.findScripts(); +var scripts = dbg.findScripts({ + source: o.script.source +}); +assertEq(scripts.length < allScripts.length, true); +assertEq(scripts.indexOf(o.script) !== -1, true); + +scripts = dbg.findScripts({ + source: o.script.source, + line: 1 +}); +assertEq(scripts.indexOf(o.script) !== -1, true); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-21.js b/js/src/jit-test/tests/debug/Debugger-findScripts-21.js new file mode 100644 index 000000000..15f7b16e6 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-21.js @@ -0,0 +1,21 @@ +// Test that delazification works after compartment merging. + +if (helperThreadCount() === 0) + quit(0); + +var g = newGlobal(); +var dbg = new Debugger(g); + +var log; +dbg.onNewScript = function (s) { + log += "s"; + log += dbg.findScripts({ source: s.source }).length; +} + +// Delazify everything just in case before we start the off-thread compile. +dbg.findScripts(); + +log = ""; +g.offThreadCompileScript("function inner() { function inner2() { print('inner2'); } print('inner'); }"); +g.runOffThreadScript(); +assertEq(log, "s3"); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-22.js b/js/src/jit-test/tests/debug/Debugger-findScripts-22.js new file mode 100644 index 000000000..543d5656f --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-22.js @@ -0,0 +1,8 @@ +// Bug 1239813: Don't let compartments get GC'd while findScripts is holding +// them in its ScriptQuery's hash set. + +var g = newGlobal(); +var dbg = new Debugger(); +dbg.addDebuggee(g); +g = newGlobal({sameZoneAs: g.Math}); +dbg.findScripts({get source() { gc(); return undefined; }}); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-23.js b/js/src/jit-test/tests/debug/Debugger-findScripts-23.js new file mode 100644 index 000000000..bc956a95d --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-23.js @@ -0,0 +1,19 @@ +// If a script is (re)lazified, findScripts should delazify it. + +var dbg = new Debugger(); + +var g = newGlobal(); +g.eval('function f(){}'); +assertEq(g.eval('isLazyFunction(f)'), true); + +dbg.addDebuggee(g); +dbg.findScripts(); +assertEq(g.eval('isLazyFunction(f)'), false); + +dbg.removeAllDebuggees(); +relazifyFunctions(); +assertEq(g.eval('isLazyFunction(f)'), true); + +dbg.addDebuggee(g); +var scripts = dbg.findScripts(); +assertEq(g.eval('isLazyFunction(f)'), false); diff --git a/js/src/jit-test/tests/debug/Debugger-findScripts-24.js b/js/src/jit-test/tests/debug/Debugger-findScripts-24.js new file mode 100644 index 000000000..eec404286 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-24.js @@ -0,0 +1,35 @@ +// findScripts should reject Debugger.Source objects from other Debuggers. + +load(libdir + 'asserts.js'); + +var g = newGlobal(); +g.evaluate(`function f() { print("earth/heart/hater"); }`, + { lineNumber: 1800 }); + +var dbg1 = new Debugger; +var gDO1 = dbg1.addDebuggee(g); +var fDO1 = gDO1.getOwnPropertyDescriptor('f').value; +assertEq(fDO1.script.source instanceof Debugger.Source, true); + +var dbg2 = new Debugger; +var gDO2 = dbg2.addDebuggee(g); +var fDO2 = gDO2.getOwnPropertyDescriptor('f').value; +assertEq(fDO2.script.source instanceof Debugger.Source, true); + +assertEq(fDO1.script.source !== fDO2.script.source, true); + +// findScripts should reject Debugger.Source objects that don't belong to the +// Debugger it's being invoked on. +assertThrowsInstanceOf(() => dbg1.findScripts({ source: fDO2.script.source }), + TypeError); +assertThrowsInstanceOf(() => dbg2.findScripts({ source: fDO1.script.source }), + TypeError); + +// findScripts should reject Debugger.Source.prototype. +assertThrowsInstanceOf(() => dbg1.findScripts({ source: Debugger.Source.prototype }), + TypeError); + +// These should not throw, and should return something, but we're not going to +// be picky about exactly what, given findScript's sensitivity to GC. +dbg1.findScripts({ source: fDO1.script.source }); +dbg2.findScripts({ source: fDO2.script.source }); diff --git a/js/src/jit-test/tests/debug/Debugger-getNewestFrame-01.js b/js/src/jit-test/tests/debug/Debugger-getNewestFrame-01.js new file mode 100644 index 000000000..54aa90f03 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-getNewestFrame-01.js @@ -0,0 +1,20 @@ +// getNewestFrame basics. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = new Debugger(g); +assertEq(dbg.getNewestFrame(), null); + +var global = this; +var frame; +function f() { + frame = dbg.getNewestFrame(); + assertEq(frame instanceof Debugger.Frame, true); + assertEq(frame.type, "eval"); + assertEq(frame.older, null); +} +g.h = this; +g.eval("h.f()"); +assertEq(frame.live, false); +assertThrowsInstanceOf(function () { frame.older; }, Error); diff --git a/js/src/jit-test/tests/debug/Debugger-getNewestFrame-02.js b/js/src/jit-test/tests/debug/Debugger-getNewestFrame-02.js new file mode 100644 index 000000000..b91afe80c --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-getNewestFrame-02.js @@ -0,0 +1,20 @@ +// Hooks and Debugger.prototype.getNewestFrame produce the same Frame object. + +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +var savedFrame, savedCallee; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame, savedFrame); + assertEq(frame.live, true); + assertEq(frame.callee, savedCallee); + hits++; +}; +g.h = function () { + savedFrame = dbg.getNewestFrame(); + savedCallee = savedFrame.callee; + assertEq(savedCallee.name, "f"); +}; +g.eval("function f() { h(); debugger; }"); +g.f(); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Debugger-getNewestFrame-03.js b/js/src/jit-test/tests/debug/Debugger-getNewestFrame-03.js new file mode 100644 index 000000000..89ec2ea52 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-getNewestFrame-03.js @@ -0,0 +1,9 @@ +// Debugger.prototype.getNewestFrame() ignores dummy frames. +// See bug 678086. + +var g = newGlobal(); +g.f = function () { return dbg.getNewestFrame(); }; +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var fw = gw.getOwnPropertyDescriptor("f").value; +assertEq(fw.call().return, null); diff --git a/js/src/jit-test/tests/debug/Debugger-isCompilableUnit.js b/js/src/jit-test/tests/debug/Debugger-isCompilableUnit.js new file mode 100644 index 000000000..dbfde0ef4 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-isCompilableUnit.js @@ -0,0 +1,58 @@ +load(libdir + "asserts.js"); + +const bad_types = [ + 2112, + {geddy: "lee"}, + () => 1, + [], + Array +] + +// We only accept strings around here! +for (var badType of bad_types) { + assertThrowsInstanceOf(() => { + Debugger.isCompilableUnit(badType); + }, TypeError); +} + +const compilable_units = [ + "wubba-lubba-dub-dub", + "'Get Schwifty!'", + "1 + 2", + "function f(x) {}", + "function x(...f,) {", // statements with bad syntax are always compilable + "let x = 100", + ";;;;;;;;", + "", + " ", + "\n", + "let x", +] + +const non_compilable_units = [ + "function f(x) {", + "(...d) =>", + "{geddy:", + "{", + "[1, 2", + "[", + "1 +", + "let x =", + "3 ==", +] + +for (var code of compilable_units) { + assertEq(Debugger.isCompilableUnit(code), true); +} + +for (var code of non_compilable_units) { + assertEq(Debugger.isCompilableUnit(code), false); +} + +// Supplying no arguments should throw a type error +assertThrowsInstanceOf(() => { + Debugger.isCompilableUnit(); +}, TypeError); + +// Supplying extra arguments should be fine +assertEq(Debugger.isCompilableUnit("", 1, 2, 3, 4, {}, []), true); diff --git a/js/src/jit-test/tests/debug/Debugger-multi-01.js b/js/src/jit-test/tests/debug/Debugger-multi-01.js new file mode 100644 index 000000000..cd29fc6de --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-multi-01.js @@ -0,0 +1,31 @@ +// When there are multiple debuggers, their hooks are called in order. + +var g = newGlobal(); +var log; +var arr = []; + +function addDebug(msg) { + var dbg = new Debugger(g); + dbg.onDebuggerStatement = function (stack) { log += msg; }; + arr.push(dbg); +} + +addDebug('a'); +addDebug('b'); +addDebug('c'); + +log = ''; +assertEq(g.eval("debugger; 0;"), 0); +assertEq(log, 'abc'); + +// Calling debugger hooks stops as soon as any hook returns a resumption value +// other than undefined. + +arr[0].onDebuggerStatement = function (stack) { + log += 'a'; + return {return: 1}; +}; + +log = ''; +assertEq(g.eval("debugger; 0;"), 1); +assertEq(log, 'a'); diff --git a/js/src/jit-test/tests/debug/Debugger-multi-02.js b/js/src/jit-test/tests/debug/Debugger-multi-02.js new file mode 100644 index 000000000..f02f0e458 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-multi-02.js @@ -0,0 +1,32 @@ +// Test adding hooks during dispatch. The behavior is deterministic and "nice", +// but mainly what we are checking here is that we do not crash due to +// modifying a data structure while we're iterating over it. + +var g = newGlobal(); +var n = 0; +var hits; + +function addDebugger() { + var dbg = new Debugger(g); + dbg.onDebuggerStatement = function (stack) { + hits++; + addDebugger(); + }; +} + +addDebugger(); // now there is one enabled Debugger +hits = 0; +g.eval("debugger;"); // after this there are two +assertEq(hits, 1); + +hits = 0; +g.eval("debugger;"); // after this there are four +assertEq(hits, 2); + +hits = 0; +g.eval("debugger;"); // after this there are eight +assertEq(hits, 4); + +hits = 0; +g.eval("debugger;"); +assertEq(hits, 8); diff --git a/js/src/jit-test/tests/debug/Debugger-multi-03.js b/js/src/jit-test/tests/debug/Debugger-multi-03.js new file mode 100644 index 000000000..f15868437 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-multi-03.js @@ -0,0 +1,21 @@ +// Q: But who shall debug the debuggers? A: jimb + +var log = ''; + +function addDebug(g, id) { + var debuggerGlobal = newGlobal(); + debuggerGlobal.debuggee = g; + debuggerGlobal.id = id; + debuggerGlobal.print = function (s) { log += s; }; + debuggerGlobal.eval( + 'var dbg = new Debugger(debuggee);\n' + + 'dbg.onDebuggerStatement = function () { print(id); debugger; print(id); };\n'); + return debuggerGlobal; +} + +var base = newGlobal(); +var top = base; +for (var i = 0; i < 8; i++) // why have 2 debuggers when you can have 8 + top = addDebug(top, i); +base.eval("debugger;"); +assertEq(log, '0123456776543210'); diff --git a/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-01.js b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-01.js new file mode 100644 index 000000000..fbe479ff3 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-01.js @@ -0,0 +1,45 @@ +// If debugger.onEnterFrame returns {return:val}, the frame returns. + +var g = newGlobal(); +g.set = false; +g.eval("function f() {\n" + + " set = true;\n" + + " return 'fail';\n" + + "}\n"); +g.eval("function g() { return 'g ' + f(); }"); +g.eval("function h() { return 'h ' + g(); }"); + +var dbg = Debugger(g); +var savedFrame; +dbg.onEnterFrame = function (frame) { + savedFrame = frame; + return {return: "pass"}; +}; + +// Call g.f as a function. +savedFrame = undefined; +assertEq(g.f(), "pass"); +assertEq(savedFrame.live, false); +assertEq(g.set, false); + +// Call g.f as a constructor. +savedFrame = undefined; +var r = new g.f; +assertEq(typeof r, "object"); +assertEq(savedFrame.live, false); +assertEq(g.set, false); + +var count = 0; +dbg.onEnterFrame = function (frame) { + count++; + if (count == 3) { + savedFrame = frame; + return {return: "pass"}; + } + return undefined; +}; +g.set = false; +savedFrame = undefined; +assertEq(g.h(), "h g pass"); +assertEq(savedFrame.live, false); +assertEq(g.set, false); diff --git a/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-02.js b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-02.js new file mode 100644 index 000000000..2b8ea2ff3 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-02.js @@ -0,0 +1,28 @@ +// If debugger.onEnterFrame returns {throw:val}, an exception is thrown in the debuggee. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +g.set = false; +g.eval("function f() {\n" + + " set = true;\n" + + " return 'fail';\n" + + "}\n"); + +var dbg = Debugger(g); +var savedFrame; +dbg.onEnterFrame = function (frame) { + savedFrame = frame; + return {throw: "pass"}; +}; + +savedFrame = undefined; +assertThrowsValue(g.f, "pass"); +assertEq(savedFrame.live, false); +assertEq(g.set, false); + +savedFrame = undefined; +assertThrowsValue(function () { new g.f; }, "pass"); +assertEq(savedFrame.live, false); +assertEq(g.set, false); + diff --git a/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-03.js b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-03.js new file mode 100644 index 000000000..a42f2183a --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-03.js @@ -0,0 +1,26 @@ +// If debugger.onEnterFrame returns {return:val}, the frame returns immediately. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +g.set = false; + +var dbg = Debugger(g); +var savedFrame; +dbg.onDebuggerStatement = function (frame) { + var innerSavedFrame; + dbg.onEnterFrame = function (frame) { + innerSavedFrame = frame; + return null; + }; + // Using frame.eval lets us catch termination. + assertEq(frame.eval("set = true;"), null); + assertEq(innerSavedFrame.live, false); + savedFrame = frame; + return { return: "pass" }; +}; + +savedFrame = undefined; +assertEq(g.eval("debugger;"), "pass"); +assertEq(savedFrame.live, false); +assertEq(g.set, false); diff --git a/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-04.js b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-04.js new file mode 100644 index 000000000..3a30949de --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-04.js @@ -0,0 +1,16 @@ +// If debugger.onEnterFrame returns undefined, the frame should continue execution. + +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +var savedFrame; +dbg.onEnterFrame = function (frame) { + hits++; + savedFrame = frame; + return undefined; +}; + +savedFrame = undefined; +assertEq(g.eval("'pass';"), "pass"); +assertEq(savedFrame.live, false); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-05.js b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-05.js new file mode 100644 index 000000000..4db69e47a --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onEnterFrame-resumption-05.js @@ -0,0 +1,98 @@ +// Exercise the call to ScriptDebugPrologue in js_InternalInterpret. + +// This may change, but as of this writing, inline caches (ICs) are +// disabled in debug mode, and those are the only users of the out-of-line entry +// points for JIT code (arityCheckEntry, argsCheckEntry, fastEntry); debug +// mode uses only invokeEntry. This means most of the bytecode tails in +// js_InternalInterpret that might call ScriptPrologue or ScriptEpilogue are +// unreachable in debug mode: they're only called from the out-of-line entry +// points. +// +// The exception is REJOIN_THIS_PROTOTYPE, which can be reached reliably if you +// add a JS_GC call to stubs::GetPropNoCache. JIT code calls that stub to +// retrieve the 'prototype' property of a function called as a constructor, if +// TI can't establish the exact identity of that prototype's value at compile +// time. Thus the preoccupation with constructors here. + +load(libdir + "asserts.js"); + +var debuggee = newGlobal(); +var dbg = Debugger(debuggee); +var hits, savedFrame; + +// Allow the constructor to return normally. +dbg.onEnterFrame = function (frame) { + hits++; + if (frame.constructing) { + savedFrame = frame; + assertEq(savedFrame.live, true); + return undefined; + } + return undefined; +}; +hits = 0; +debuggee.hits = 0; +savedFrame = undefined; +assertEq(typeof debuggee.eval("function f(){ hits++; } f.prototype = {}; new f;"), "object"); +assertEq(hits, 2); +assertEq(savedFrame.live, false); +assertEq(debuggee.hits, 1); + +// Force an early return from the constructor. +dbg.onEnterFrame = function (frame) { + hits++; + if (frame.constructing) { + savedFrame = frame; + assertEq(savedFrame.live, true); + return { return: "pass" }; + } + return undefined; +}; +hits = 0; +debuggee.hits = 0; +savedFrame = undefined; +assertEq(typeof debuggee.eval("function f(){ hits++; } f.prototype = {}; new f;"), "object"); +assertEq(hits, 2); +assertEq(savedFrame.live, false); +assertEq(debuggee.hits, 0); + +// Force the constructor to throw an exception. +dbg.onEnterFrame = function (frame) { + hits++; + if (frame.constructing) { + savedFrame = frame; + assertEq(savedFrame.live, true); + return { throw: "pass" }; + } + return undefined; +}; +hits = 0; +debuggee.hits = 0; +savedFrame = undefined; +assertThrowsValue(function () { + debuggee.eval("function f(){ hits++ } f.prototype = {}; new f;"); + }, "pass"); +assertEq(hits, 2); +assertEq(savedFrame.live, false); +assertEq(debuggee.hits, 0); + +// Ensure that forcing an early return only returns from one JS call. +debuggee.eval("function g() { var result = new f; g_hits++; return result; }"); +dbg.onEnterFrame = function (frame) { + hits++; + if (frame.constructing) { + savedFrame = frame; + assertEq(savedFrame.live, true); + return { return: "pass" }; + } + return undefined; +}; +hits = 0; +debuggee.hits = 0; +debuggee.g_hits = 0; +savedFrame = undefined; +assertEq(typeof debuggee.eval("g();"), "object"); +assertEq(hits, 3); +assertEq(savedFrame.live, false); +assertEq(debuggee.hits, 0); +assertEq(debuggee.g_hits, 1); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-01.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-01.js new file mode 100644 index 000000000..736d29ad0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-01.js @@ -0,0 +1,64 @@ +// Debugger.prototype.onNewGlobalObject surfaces. + +load(libdir + 'asserts.js'); + +var dbg = new Debugger; + +function f() { } +function g() { } + +assertEq(Object.getOwnPropertyDescriptor(dbg, 'onNewGlobalObject'), undefined); + +var d = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(dbg), 'onNewGlobalObject'); +assertEq(d.enumerable, false); +assertEq(d.configurable, true); +assertEq(typeof d.get, "function"); +assertEq(typeof d.set, "function"); + +assertEq(dbg.onNewGlobalObject, undefined); + +assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = ''; }, TypeError); +assertEq(dbg.onNewGlobalObject, undefined); + +assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = false; }, TypeError); +assertEq(dbg.onNewGlobalObject, undefined); + +assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = 0; }, TypeError); +assertEq(dbg.onNewGlobalObject, undefined); + +assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = Math.PI; }, TypeError); +assertEq(dbg.onNewGlobalObject, undefined); + +assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = null; }, TypeError); +assertEq(dbg.onNewGlobalObject, undefined); + +assertThrowsInstanceOf(function () { dbg.onNewGlobalObject = {}; }, TypeError); +assertEq(dbg.onNewGlobalObject, undefined); + +// But any function, even a useless one, is okay. How fair is that? +dbg.onNewGlobalObject = f; +assertEq(dbg.onNewGlobalObject, f); + +dbg.onNewGlobalObject = undefined; +assertEq(dbg.onNewGlobalObject, undefined); + +var dbg2 = new Debugger; +assertEq(dbg.onNewGlobalObject, undefined); +assertEq(dbg2.onNewGlobalObject, undefined); + +dbg.onNewGlobalObject = f; +assertEq(dbg.onNewGlobalObject, f); +assertEq(dbg2.onNewGlobalObject, undefined); + +dbg2.onNewGlobalObject = g; +assertEq(dbg.onNewGlobalObject, f); +assertEq(dbg2.onNewGlobalObject, g); + +dbg.onNewGlobalObject = undefined; +assertEq(dbg.onNewGlobalObject, undefined); +assertEq(dbg2.onNewGlobalObject, g); + +// You shouldn't be able to apply the accessor to the prototype. +assertThrowsInstanceOf(function () { + Debugger.prototype.onNewGlobalObject = function () { }; + }, TypeError); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-02.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-02.js new file mode 100644 index 000000000..c8c3d74ad --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-02.js @@ -0,0 +1,23 @@ +// onNewGlobalObject handlers fire, until they are removed. + +var dbg = new Debugger; +var log; + +log = ''; +newGlobal(); +assertEq(log, ''); + +dbg.onNewGlobalObject = function (global) { + log += 'n'; + assertEq(global.seen, undefined); + global.seen = true; +}; + +log = ''; +newGlobal(); +assertEq(log, 'n'); + +log = ''; +dbg.onNewGlobalObject = undefined; +newGlobal(); +assertEq(log, ''); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-03.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-03.js new file mode 100644 index 000000000..76a3649a2 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-03.js @@ -0,0 +1,40 @@ +// onNewGlobalObject handlers on different Debugger instances are independent. + +var dbg1 = new Debugger; +var log1; +function h1(global) { + log1 += 'n'; + assertEq(global.seen, undefined); + global.seen = true; +} + +var dbg2 = new Debugger; +var log2; +function h2(global) { + log2 += 'n'; + assertEq(global.seen, undefined); + global.seen = true; +} + +log1 = log2 = ''; +newGlobal(); +assertEq(log1, ''); +assertEq(log2, ''); + +log1 = log2 = ''; +dbg1.onNewGlobalObject = h1; +newGlobal(); +assertEq(log1, 'n'); +assertEq(log2, ''); + +log1 = log2 = ''; +dbg2.onNewGlobalObject = h2; +newGlobal(); +assertEq(log1, 'n'); +assertEq(log2, 'n'); + +log1 = log2 = ''; +dbg1.onNewGlobalObject = undefined; +newGlobal(); +assertEq(log1, ''); +assertEq(log2, 'n'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-04.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-04.js new file mode 100644 index 000000000..096a2dead --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-04.js @@ -0,0 +1,24 @@ +// onNewGlobalObject handlers only fire on enabled Debuggers. + +var dbg = new Debugger; +var log; + +dbg.onNewGlobalObject = function (global) { + log += 'n'; + assertEq(global.seen, undefined); + global.seen = true; +}; + +log = ''; +newGlobal(); +assertEq(log, 'n'); + +log = ''; +dbg.enabled = false; +newGlobal(); +assertEq(log, ''); + +log = ''; +dbg.enabled = true; +newGlobal(); +assertEq(log, 'n'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-05.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-05.js new file mode 100644 index 000000000..4ec3edc19 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-05.js @@ -0,0 +1,13 @@ +// An onNewGlobalObject handler can disable itself. + +var dbg = new Debugger; +var log; + +dbg.onNewGlobalObject = function (global) { + log += 'n'; + dbg.onNewGlobalObject = undefined; +}; + +log = ''; +newGlobal(); +assertEq(log, 'n'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-06.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-06.js new file mode 100644 index 000000000..f58f4e7ee --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-06.js @@ -0,0 +1,20 @@ +// One Debugger's onNewGlobalObject handler can disable another Debugger's handler. + +var dbg1 = new Debugger; +var dbg2 = new Debugger; +var dbg3 = new Debugger; +var log; +var hit; + +function handler(global) { + hit++; + log += hit; + if (hit == 2) + dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = undefined; +}; + +log = ''; +hit = 0; +dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = handler; +newGlobal(); +assertEq(log, '12'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-07.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-07.js new file mode 100644 index 000000000..15c8568e0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-07.js @@ -0,0 +1,20 @@ +// One Debugger's onNewGlobalObject handler can disable other Debuggers. + +var dbg1 = new Debugger; +var dbg2 = new Debugger; +var dbg3 = new Debugger; +var log; +var hit; + +function handler(global) { + hit++; + log += hit; + if (hit == 2) + dbg1.enabled = dbg2.enabled = dbg3.enabled = false; +}; + +log = ''; +hit = 0; +dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = handler; +newGlobal(); +assertEq(log, '12'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-08.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-08.js new file mode 100644 index 000000000..f981e8ae0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-08.js @@ -0,0 +1,26 @@ +// Creating a global within an onNewGlobalObject handler causes a recursive handler invocation. +// +// This isn't really desirable behavior, as presumably a global created while a +// handler is running is one the debugger is creating for its own purposes and +// should not be observed, but if this behavior changes, we sure want to know. + +var dbg = new Debugger; +var log; +var depth; + +dbg.onNewGlobalObject = function (global) { + log += '('; depth++; + + assertEq(global.seen, undefined); + global.seen = true; + + if (depth < 3) + newGlobal(); + + log += ')'; depth--; +}; + +log = ''; +depth = 0; +newGlobal(); +assertEq(log, '((()))'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-09.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-09.js new file mode 100644 index 000000000..1c4f02972 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-09.js @@ -0,0 +1,34 @@ +// Resumption values from onNewGlobalObject handlers are disallowed. + +load(libdir + 'asserts.js'); + +var dbg = new Debugger; +var log; + +dbg.onNewGlobalObject = function (g) { log += 'n'; return undefined; }; +log = ''; +assertEq(typeof newGlobal(), "object"); +assertEq(log, 'n'); + +dbg.uncaughtExceptionHook = function (ex) { assertEq(/disallowed/.test(ex), true); log += 'u'; } +dbg.onNewGlobalObject = function (g) { log += 'n'; return { return: "snoo" }; }; +log = ''; +assertEq(typeof newGlobal(), "object"); +assertEq(log, 'nu'); + +dbg.onNewGlobalObject = function (g) { log += 'n'; return { throw: "snoo" }; }; +log = ''; +assertEq(typeof newGlobal(), "object"); +assertEq(log, 'nu'); + +dbg.onNewGlobalObject = function (g) { log += 'n'; return null; }; +log = ''; +assertEq(typeof newGlobal(), "object"); +assertEq(log, 'nu'); + +dbg.uncaughtExceptionHook = function (ex) { assertEq(/foopy/.test(ex), true); log += 'u'; } +dbg.onNewGlobalObject = function (g) { log += 'n'; throw "foopy"; }; +log = ''; +assertEq(typeof newGlobal(), "object"); +assertEq(log, 'nu'); + diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-10.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-10.js new file mode 100644 index 000000000..ce90ed541 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-10.js @@ -0,0 +1,27 @@ +// An earlier onNewGlobalObject handler returning a 'throw' resumption +// value causes later handlers not to run. + +load(libdir + 'asserts.js'); + +var dbg1 = new Debugger; +var dbg2 = new Debugger; +var dbg3 = new Debugger; +var log; +var count; + +dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = function (global) { + count++; + log += count; + if (count == 2) + return { throw: "snoo" }; + return undefined; +}; +dbg2.uncaughtExceptionHook = function (exn) { + assertEq(/disallowed/.test(exn), true); + log += 'u'; +}; + +log = ''; +count = 0; +assertEq(typeof newGlobal(), "object"); +assertEq(log, '12u3'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-11.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-11.js new file mode 100644 index 000000000..33345980d --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-11.js @@ -0,0 +1,31 @@ +// Resumption values other than |undefined| from uncaughtExceptionHook from +// onNewGlobalObject handlers are ignored (other than cancelling further hooks). + +load(libdir + 'asserts.js'); + +var dbg = new Debugger; +var log; + +dbg.onNewGlobalObject = function () { + log += 'n'; + throw 'party'; +}; + +dbg.uncaughtExceptionHook = function (ex) { + log += 'u'; + assertEq(ex, 'party'); + return { throw: 'fit' }; +}; + +log = ''; +assertEq(typeof newGlobal(), 'object'); +assertEq(log, 'nu'); + +dbg.uncaughtExceptionHook = function (ex) { + log += 'u'; + assertEq(ex, 'party'); +}; + +log = ''; +assertEq(typeof newGlobal(), 'object'); +assertEq(log, 'nu'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-12.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-12.js new file mode 100644 index 000000000..c40861811 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-12.js @@ -0,0 +1,29 @@ +// Resumption values from uncaughtExceptionHook from onNewGlobalObject +// handlers affect the dispatch of the event to other Debugger instances. + +load(libdir + 'asserts.js'); + +var dbg1 = new Debugger; +var dbg2 = new Debugger; +var dbg3 = new Debugger; +var log; +var count; + +dbg1.onNewGlobalObject = dbg2.onNewGlobalObject = dbg3.onNewGlobalObject = function () { + log += 'n'; + throw 'party'; +}; + +dbg1.uncaughtExceptionHook = dbg2.uncaughtExceptionHook = dbg3.uncaughtExceptionHook = +function (ex) { + log += 'u'; + assertEq(ex, 'party'); + if (++count == 2) + return { throw: 'fit' }; + return undefined; +}; + +log = ''; +count = 0; +assertEq(typeof newGlobal(), 'object'); +assertEq(log, 'nunu'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-13.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-13.js new file mode 100644 index 000000000..1ebd0b606 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-13.js @@ -0,0 +1,17 @@ +// onNewGlobalObject handlers receive the correct Debugger.Object instances. + +var dbg = new Debugger; + +var gw = null; +dbg.onNewGlobalObject = function (global) { + assertEq(arguments.length, 1); + assertEq(this, dbg); + gw = global; +}; +var g = newGlobal(); +assertEq(typeof gw, 'object'); +assertEq(dbg.addDebuggee(g), gw); + +// The Debugger.Objects passed to onNewGlobalObject are the global as +// viewed from its own compartment. +assertEq(gw.makeDebuggeeValue(g), gw); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-14.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-14.js new file mode 100644 index 000000000..4415469ff --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-14.js @@ -0,0 +1,17 @@ +// Globals passed to onNewGlobalObject handers are ready for use immediately. + +var dbg = new Debugger; +var log = ''; +dbg.onNewGlobalObject = function (global) { + log += 'n'; + var gw = dbg.addDebuggee(global); + gw.defineProperty('x', { value: -1 }); + // Check that the global's magic lazy properties are working. + assertEq(gw.executeInGlobalWithBindings('Math.atan2(y,x)', { y: 0 }).return, Math.PI); + // Check that the global's prototype is hooked up. + assertEq(gw.executeInGlobalWithBindings('x.toString()', { x: gw }).return, "[object global]"); +}; + +newGlobal(); + +assertEq(log, 'n'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-15.js b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-15.js new file mode 100644 index 000000000..16431a406 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-15.js @@ -0,0 +1,24 @@ +// Globals marked as invisibleToDebugger behave appropriately. + +load(libdir + "asserts.js"); + +var dbg = new Debugger; +var log = ''; +dbg.onNewGlobalObject = function (global) { + log += 'n'; +} + +assertEq(typeof newGlobal(), "object"); +assertEq(typeof newGlobal({invisibleToDebugger: false}), "object"); +assertEq(log, 'nn'); + +log = ''; +assertEq(typeof newGlobal({invisibleToDebugger: true}), "object"); +assertEq(log, ''); + +assertThrowsInstanceOf(() => dbg.addDebuggee(newGlobal({invisibleToDebugger: true})), Error); + +var glob = newGlobal({invisibleToDebugger: true}); +dbg.addAllGlobalsAsDebuggees(); +dbg.onDebuggerStatement = function (frame) { assertEq(true, false); }; +glob.eval('debugger'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js b/js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js new file mode 100644 index 000000000..07081168c --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-01.js @@ -0,0 +1,19 @@ +// Test that the onNewPromise hook gets called when promises are allocated in +// the scope of debuggee globals. +if (!('Promise' in this)) + quit(0); + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + + +let promisesFound = []; +dbg.onNewPromise = p => { promisesFound.push(p); }; + +let p1 = new g.Promise(function (){}); +dbg.enabled = false; +let p2 = new g.Promise(function (){}); + +assertEq(promisesFound.indexOf(gw.makeDebuggeeValue(p1)) != -1, true); +assertEq(promisesFound.indexOf(gw.makeDebuggeeValue(p2)) == -1, true); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewPromise-02.js b/js/src/jit-test/tests/debug/Debugger-onNewPromise-02.js new file mode 100644 index 000000000..21db892d8 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-02.js @@ -0,0 +1,26 @@ +// onNewPromise handlers fire, until they are removed. +if (!('Promise' in this)) + quit(0); + +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +log = ''; +new g.Promise(function (){}); +assertEq(log, ''); + +dbg.onNewPromise = function (promise) { + log += 'n'; + assertEq(promise.seen, undefined); + promise.seen = true; +}; + +log = ''; +new g.Promise(function (){}); +assertEq(log, 'n'); + +log = ''; +dbg.onNewPromise = undefined; +new g.Promise(function (){}); +assertEq(log, ''); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewPromise-03.js b/js/src/jit-test/tests/debug/Debugger-onNewPromise-03.js new file mode 100644 index 000000000..377855a1f --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-03.js @@ -0,0 +1,43 @@ +// onNewPromise handlers on different Debugger instances are independent. +if (!('Promise' in this)) + quit(0); + +var g = newGlobal(); +var dbg1 = new Debugger(g); +var log1; +function h1(promise) { + log1 += 'n'; + assertEq(promise.seen, undefined); + promise.seen = true; +} + +var dbg2 = new Debugger(g); +var log2; +function h2(promise) { + log2 += 'n'; + assertEq(promise.seen, undefined); + promise.seen = true; +} + +log1 = log2 = ''; +new g.Promise(function (){}); +assertEq(log1, ''); +assertEq(log2, ''); + +log1 = log2 = ''; +dbg1.onNewPromise = h1; +new g.Promise(function (){}); +assertEq(log1, 'n'); +assertEq(log2, ''); + +log1 = log2 = ''; +dbg2.onNewPromise = h2; +new g.Promise(function (){}); +assertEq(log1, 'n'); +assertEq(log2, 'n'); + +log1 = log2 = ''; +dbg1.onNewPromise = undefined; +new g.Promise(function (){}); +assertEq(log1, ''); +assertEq(log2, 'n'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewPromise-04.js b/js/src/jit-test/tests/debug/Debugger-onNewPromise-04.js new file mode 100644 index 000000000..410a0add9 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-04.js @@ -0,0 +1,17 @@ +// An onNewPromise handler can disable itself. +if (!('Promise' in this)) + quit(0); + +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +dbg.onNewPromise = function (promise) { + log += 'n'; + dbg.onNewPromise = undefined; +}; + +log = ''; +new g.Promise(function (){}); +new g.Promise(function (){}); +assertEq(log, 'n'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewPromise-05.js b/js/src/jit-test/tests/debug/Debugger-onNewPromise-05.js new file mode 100644 index 000000000..29edfeedd --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-05.js @@ -0,0 +1,27 @@ +// Creating a promise within an onNewPromise handler causes a recursive handler +// invocation. +if (!('Promise' in this)) + quit(0); + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); +var log; +var depth; + +dbg.onNewPromise = function (promise) { + log += '('; depth++; + + assertEq(promise.seen, undefined); + promise.seen = true; + + if (depth < 3) + gw.executeInGlobal(`new Promise(_=>{})`); + + log += ')'; depth--; +}; + +log = ''; +depth = 0; +new g.Promise(function (){}); +assertEq(log, '((()))'); diff --git a/js/src/jit-test/tests/debug/Debugger-onNewPromise-06.js b/js/src/jit-test/tests/debug/Debugger-onNewPromise-06.js new file mode 100644 index 000000000..7e5b9927a --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-06.js @@ -0,0 +1,37 @@ +// Resumption values from onNewPromise handlers are disallowed. +if (!('Promise' in this)) + quit(0); + +load(libdir + 'asserts.js'); + +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +dbg.onNewPromise = function (g) { log += 'n'; return undefined; }; +log = ''; +assertEq(typeof new g.Promise(function (){}), "object"); +assertEq(log, 'n'); + +dbg.uncaughtExceptionHook = function (ex) { assertEq(/disallowed/.test(ex), true); log += 'u'; } +dbg.onNewPromise = function (g) { log += 'n'; return { return: "snoo" }; }; +log = ''; +assertEq(typeof new g.Promise(function (){}), "object"); +assertEq(log, 'nu'); + +dbg.onNewPromise = function (g) { log += 'n'; return { throw: "snoo" }; }; +log = ''; +assertEq(typeof new g.Promise(function (){}), "object"); +assertEq(log, 'nu'); + +dbg.onNewPromise = function (g) { log += 'n'; return null; }; +log = ''; +assertEq(typeof new g.Promise(function (){}), "object"); +assertEq(log, 'nu'); + +dbg.uncaughtExceptionHook = function (ex) { assertEq(/foopy/.test(ex), true); log += 'u'; } +dbg.onNewPromise = function (g) { log += 'n'; throw "foopy"; }; +log = ''; +assertEq(typeof new g.Promise(function (){}), "object"); +assertEq(log, 'nu'); + diff --git a/js/src/jit-test/tests/debug/Debugger-onNewPromise-07.js b/js/src/jit-test/tests/debug/Debugger-onNewPromise-07.js new file mode 100644 index 000000000..495f356c6 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onNewPromise-07.js @@ -0,0 +1,15 @@ +// Errors in onNewPromise handlers are reported correctly, and don't mess up the +// promise creation. +if (!('Promise' in this)) + quit(0); + +var g = newGlobal(); +var dbg = new Debugger(g); + +let e; +dbg.uncaughtExceptionHook = ee => { e = ee; }; +dbg.onNewPromise = () => { throw new Error("woops!"); }; + +assertEq(typeof new g.Promise(function (){}), "object"); +assertEq(!!e, true); +assertEq(!!e.message.match(/woops/), true); diff --git a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js new file mode 100644 index 000000000..7277ed8ae --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-01.js @@ -0,0 +1,27 @@ +// Test that the onPromiseSettled hook gets called when a promise settles. +if (!('Promise' in this)) + quit(0); + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + +let log = ""; +let pw; +dbg.onPromiseSettled = pw_ => { + pw = pw_; + log += "s"; +}; + +let p = new g.Promise(function (){}); +g.settlePromiseNow(p); + +assertEq(log, "s"); +assertEq(pw, gw.makeDebuggeeValue(p)); + +log = ""; +dbg.enabled = false; +p = new g.Promise(function (){}); +g.settlePromiseNow(p); + +assertEq(log, ""); diff --git a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-02.js b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-02.js new file mode 100644 index 000000000..febca8f78 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-02.js @@ -0,0 +1,26 @@ +// onPromiseSettled handlers fire, until they are removed. +if (!('Promise' in this)) + quit(0); + +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +log = ''; +g.settlePromiseNow(new g.Promise(function (){})); +assertEq(log, ''); + +dbg.onPromiseSettled = function (promise) { + log += 's'; + assertEq(promise.seen, undefined); + promise.seen = true; +}; + +log = ''; +g.settlePromiseNow(new g.Promise(function (){})); +assertEq(log, 's'); + +log = ''; +dbg.onPromiseSettled = undefined; +g.settlePromiseNow(new g.Promise(function (){})); +assertEq(log, ''); diff --git a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-03.js b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-03.js new file mode 100644 index 000000000..34e9f2417 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-03.js @@ -0,0 +1,43 @@ +// onPromiseSettled handlers on different Debugger instances are independent. +if (!('Promise' in this)) + quit(0); + +var g = newGlobal(); +var dbg1 = new Debugger(g); +var log1; +function h1(promise) { + log1 += 's'; + assertEq(promise.seen, undefined); + promise.seen = true; +} + +var dbg2 = new Debugger(g); +var log2; +function h2(promise) { + log2 += 's'; + assertEq(promise.seen, undefined); + promise.seen = true; +} + +log1 = log2 = ''; +g.settlePromiseNow(new g.Promise(function (){})); +assertEq(log1, ''); +assertEq(log2, ''); + +log1 = log2 = ''; +dbg1.onPromiseSettled = h1; +g.settlePromiseNow(new g.Promise(function (){})); +assertEq(log1, 's'); +assertEq(log2, ''); + +log1 = log2 = ''; +dbg2.onPromiseSettled = h2; +g.settlePromiseNow(new g.Promise(function (){})); +assertEq(log1, 's'); +assertEq(log2, 's'); + +log1 = log2 = ''; +dbg1.onPromiseSettled = undefined; +g.settlePromiseNow(new g.Promise(function (){})); +assertEq(log1, ''); +assertEq(log2, 's'); diff --git a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-04.js b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-04.js new file mode 100644 index 000000000..86df066c8 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-04.js @@ -0,0 +1,17 @@ +// An onPromiseSettled handler can disable itself. +if (!('Promise' in this)) + quit(0); + +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +dbg.onPromiseSettled = function (promise) { + log += 's'; + dbg.onPromiseSettled = undefined; +}; + +log = ''; +g.settlePromiseNow(new g.Promise(function (){})); +g.settlePromiseNow(new g.Promise(function (){})); +assertEq(log, 's'); diff --git a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-05.js b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-05.js new file mode 100644 index 000000000..da358efe8 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-05.js @@ -0,0 +1,27 @@ +// Settling a promise within an onPromiseSettled handler causes a recursive +// handler invocation. +if (!('Promise' in this)) + quit(0); + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); +var log; +var depth; + +dbg.onPromiseSettled = function (promise) { + log += '('; depth++; + + assertEq(promise.seen, undefined); + promise.seen = true; + + if (depth < 3) { + gw.executeInGlobal(`settlePromiseNow(new Promise(_=>{}));`); + } + log += ')'; depth--; +}; + +log = ''; +depth = 0; +g.settlePromiseNow(new g.Promise(_=>{})); +assertEq(log, '((()))'); diff --git a/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-06.js b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-06.js new file mode 100644 index 000000000..97108bef4 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-onPromiseSettled-06.js @@ -0,0 +1,37 @@ +// Resumption values from onPromiseSettled handlers are disallowed. +if (!('Promise' in this)) + quit(0); + +load(libdir + 'asserts.js'); + +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +dbg.onPromiseSettled = function (g) { log += 's'; return undefined; }; +log = ''; +g.settlePromiseNow(new g.Promise(function (){})); +assertEq(log, 's'); + +dbg.uncaughtExceptionHook = function (ex) { assertEq(/disallowed/.test(ex), true); log += 'u'; } +dbg.onPromiseSettled = function (g) { log += 's'; return { return: "snoo" }; }; +log = ''; +g.settlePromiseNow(new g.Promise(function (){})); +assertEq(log, 'su'); + +dbg.onPromiseSettled = function (g) { log += 's'; return { throw: "snoo" }; }; +log = ''; +g.settlePromiseNow(new g.Promise(function (){})); +assertEq(log, 'su'); + +dbg.onPromiseSettled = function (g) { log += 's'; return null; }; +log = ''; +g.settlePromiseNow(new g.Promise(function (){})); +assertEq(log, 'su'); + +dbg.uncaughtExceptionHook = function (ex) { assertEq(/foopy/.test(ex), true); log += 'u'; } +dbg.onPromiseSettled = function (g) { log += 's'; throw "foopy"; }; +log = ''; +g.settlePromiseNow(new g.Promise(function (){})); +assertEq(log, 'su'); + 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 000000000..7baee2aed --- /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(); +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 000000000..e78edff0a --- /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(); +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-03.js b/js/src/jit-test/tests/debug/Environment-03.js new file mode 100644 index 000000000..857c20716 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-03.js @@ -0,0 +1,10 @@ +// Test that getting a function's environment can unlazify scripts. + +var g = newGlobal(); +g.eval('function f() { }'); +var dbg = new Debugger; +var gw = dbg.makeGlobalObjectReference(g); +var fw = gw.getOwnPropertyDescriptor('f').value; +gc(); +dbg.addDebuggee(g); +var fenv = fw.environment; diff --git a/js/src/jit-test/tests/debug/Environment-Function-prototype.js b/js/src/jit-test/tests/debug/Environment-Function-prototype.js new file mode 100644 index 000000000..9ba64a26c --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-Function-prototype.js @@ -0,0 +1,7 @@ +// Don't crash when getting the Debugger.Environment of a frame inside +// Function.prototype. + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onEnterFrame = function (frame) { frame.environment; }; +g.Function.prototype(); diff --git a/js/src/jit-test/tests/debug/Environment-callee-01.js b/js/src/jit-test/tests/debug/Environment-callee-01.js new file mode 100644 index 000000000..424e9164a --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-callee-01.js @@ -0,0 +1,48 @@ +// Debugger.Environment.prototype.callee reveals the callee of environments +// that have them. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +function check(code, expectedType, expectedCallee) { + print("check(" + uneval(code) + ")"); + var hits; + dbg.onDebuggerStatement = function (frame) { + hits++; + var env = frame.environment; + assertEq(env.type, expectedType); + assertEq(env.callee, expectedCallee); + }; + hits = 0; + g.eval(code); + assertEq(hits, 1); +} + +check('debugger;', 'declarative', null); +check('with({}) { debugger; };', 'with', null); +check('{ let x=1; debugger; };', 'declarative', null); + +g.eval('function f() { debugger; }'); +check('f()', 'declarative', gw.makeDebuggeeValue(g.f)); + +g.eval('function g() { h(); }'); +g.eval('function h() { debugger; }'); +check('g()', 'declarative', gw.makeDebuggeeValue(g.h)); + +// All evals get a lexical scope. +check('"use strict"; eval("debugger");', 'declarative', null); +g.eval('function j() { "use strict"; eval("debugger;"); }'); +check('j()', 'declarative', null); + +// All evals get a lexical scope. +check('eval("debugger");', 'declarative', null); + +g.eval('function m() { debugger; yield true; }'); +check('m().next();', 'declarative', gw.makeDebuggeeValue(g.m)); + +g.eval('function n() { { let x = 1; debugger; } }'); +check('n()', 'declarative', null); + +g.eval('function* o() { debugger; yield true; }'); +check('o().next();', 'declarative', gw.makeDebuggeeValue(g.o)); diff --git a/js/src/jit-test/tests/debug/Environment-callee-02.js b/js/src/jit-test/tests/debug/Environment-callee-02.js new file mode 100644 index 000000000..da6ceddbf --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-callee-02.js @@ -0,0 +1,25 @@ +// Debugger.Environment.prototype.callee gets the right closure. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +g.eval('function f(x) { return function (y) { debugger; return x + y; } }'); +g.eval('var g = f(2);'); +g.eval('var h = f(3);'); + +function check(fun, label) { + print("check(" + label + ")"); + var hits; + dbg.onDebuggerStatement = function (frame) { + hits++; + var env = frame.environment; + assertEq(env.callee, gw.makeDebuggeeValue(fun)); + }; + hits = 0; + fun(); + assertEq(hits, 1); +} + +check(g.g, 'g.g'); +check(g.h, 'g.h'); diff --git a/js/src/jit-test/tests/debug/Environment-callee-03.js b/js/src/jit-test/tests/debug/Environment-callee-03.js new file mode 100644 index 000000000..6369817a8 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-callee-03.js @@ -0,0 +1,31 @@ +// Environments of different instances of the same generator have the same +// callee. I love this job. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +function check(gen, label) { + print("check(" + label + ")"); + var hits; + dbg.onDebuggerStatement = function (frame) { + hits++; + var env = frame.environment; + assertEq(env.callee, gw.makeDebuggeeValue(g.f)); + }; + hits = 0; + gen.next(); + assertEq(hits, 1); +} + +g.eval('function f(x) { debugger; yield x; }'); +g.eval('var g = f(2);'); +g.eval('var h = f(3);'); +check(g.g, 'g.g'); +check(g.h, 'g.h'); + +g.eval('function* f(x) { debugger; yield x; }'); +g.eval('var g = f(2);'); +g.eval('var h = f(3);'); +check(g.g, 'g.g'); +check(g.h, 'g.h'); diff --git a/js/src/jit-test/tests/debug/Environment-callee-04.js b/js/src/jit-test/tests/debug/Environment-callee-04.js new file mode 100644 index 000000000..b2b9534d0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-callee-04.js @@ -0,0 +1,22 @@ +// We shouldn't hand out environment callees when we can only provide the +// internal function object, not the live function object. (We should never +// create Debugger.Object instances referring to internal function objects.) + +var g = newGlobal(); +var dbg = new Debugger(g); + +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.older.environment.parent.callee, null); +} + +g.evaluate(` + + function h() { debugger; } + (function () { + return function () { + h(); + return 1; + } + })()(); + + `); 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 000000000..497fceb2a --- /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(); +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.environment.find("x").type, "with"); + 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 000000000..e6e0e80c9 --- /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(); +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 () { 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 000000000..fd07f603f --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-find-03.js @@ -0,0 +1,20 @@ +// env.find() finds nonenumerable properties in with statements. + +var g = newGlobal(); +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 000000000..071420243 --- /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(); +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 000000000..e69de29bb --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-find-05.js 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 000000000..b012dbe0a --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-find-06.js @@ -0,0 +1,47 @@ +// Environment.prototype.find finds bindings that are function arguments, 'let' +// bindings, or FunctionExpression names. + +var g = newGlobal(); +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 < 2048; 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();'); + +test1('(function X() { h(); })();'); +test1('(function X(a, b, c) { h(); })(1, 2, 3);'); diff --git a/js/src/jit-test/tests/debug/Environment-find-07.js b/js/src/jit-test/tests/debug/Environment-find-07.js new file mode 100644 index 000000000..1737f29fd --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-find-07.js @@ -0,0 +1,22 @@ +// We can find into and from optimized out scopes. + +var g = newGlobal(); +var dbg = new Debugger; +dbg.addDebuggee(g); + +g.eval("" + function f() { + var x = 42; + function g() { } + g(); +}); + +dbg.onEnterFrame = function (f) { + if (f.callee && (f.callee.name === "g")) { + genv = f.environment.parent; + assertEq(genv.optimizedOut, true); + assertEq(genv.find("f").type, "object"); + assertEq(f.environment.find("x"), genv); + } +} + +g.f(); 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 000000000..1fbd8b9ef --- /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(); +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(","), "arguments,x"); + +gc(); +g.gc(g); +gc(env); + +assertEq(env.find("x"), env); +assertEq(env.names().join(","), "arguments,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 000000000..b2d38ef84 --- /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(); +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-gc-03.js b/js/src/jit-test/tests/debug/Environment-gc-03.js new file mode 100644 index 000000000..e3bbad052 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-gc-03.js @@ -0,0 +1,21 @@ +// Test that block scopes cannot be resurrected by onStep. + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onDebuggerStatement = function(frame) { + frame.onStep = (function() { + frame.environment; + }); +}; + +g.eval("debugger; for (let i = 0; i < 1; i++) (function(){});"); + +// If the last freshened block scope was incorrectly resurrected by onStep +// above, GCing will assert. +gc(); + +g.eval("debugger; { let i = 0; (function(){ i = 42; }); }"); +gc(); + +g.eval("debugger; try { throw 42; } catch (e) { };"); +gc(); diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-01.js b/js/src/jit-test/tests/debug/Environment-getVariable-01.js new file mode 100644 index 000000000..e3413e7f8 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-01.js @@ -0,0 +1,14 @@ +// Environment.prototype.getVariable does not see variables bound in enclosing scopes. + +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.environment.getVariable("x"), 13); + assertEq(frame.environment.getVariable("k"), undefined); + assertEq(frame.environment.find("k").getVariable("k"), 3); + hits++; +}; +g.eval("var k = 3; function f(x) { debugger; }"); +g.f(13); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-02.js b/js/src/jit-test/tests/debug/Environment-getVariable-02.js new file mode 100644 index 000000000..c3aba311d --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-02.js @@ -0,0 +1,18 @@ +// getVariable works in function scopes. + +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var lexicalEnv = frame.environment; + var varEnv = lexicalEnv.parent; + assertEq(varEnv.getVariable("a"), 1); + assertEq(varEnv.getVariable("b"), 2); + assertEq(varEnv.getVariable("c"), 3); + assertEq(varEnv.getVariable("d"), 4); + assertEq(lexicalEnv.getVariable("e"), 5); + hits++; +}; +g.eval("function f(a, [b, c]) { var d = c + 1; let e = d + 1; debugger; }"); +g.f(1, [2, 3]); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-03.js b/js/src/jit-test/tests/debug/Environment-getVariable-03.js new file mode 100644 index 000000000..b47fe65c1 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-03.js @@ -0,0 +1,21 @@ +// getVariable sees bindings in let-block scopes. + +var g = newGlobal(); +var dbg = Debugger(g); +var log = ''; +dbg.onDebuggerStatement = function (frame) { + log += frame.environment.getVariable("x"); +}; +g.eval("function f() {\n" + + " let x = 'a';\n" + + " debugger;\n" + + " for (let x = 0; x < 2; x++)\n" + + " if (x === 0)\n" + + " debugger;\n" + + " else {\n" + + " let x = 'b'; debugger;\n" + + " }\n" + + "}\n"); +g.f(); +g.eval("{ let x = 'd'; debugger; }"); +assertEq(log, 'a0bd'); diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-04.js b/js/src/jit-test/tests/debug/Environment-getVariable-04.js new file mode 100644 index 000000000..22875cb0b --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-04.js @@ -0,0 +1,12 @@ +// getVariable sees variables in function scopes added by non-strict direct eval. + +var g = newGlobal(); +var dbg = Debugger(g); +var v; +dbg.onDebuggerStatement = function (frame) { + v = frame.environment.getVariable("x"); +}; + +g.eval("function f(s) { eval(s); debugger; }"); +g.f("var x = 'Q';"); +assertEq(v, 'Q'); diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-05.js b/js/src/jit-test/tests/debug/Environment-getVariable-05.js new file mode 100644 index 000000000..f4bd52144 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-05.js @@ -0,0 +1,10 @@ +// getVariable sees global variables. + +var g = newGlobal(); +var dbg = Debugger(g); +var log = ''; +dbg.onDebuggerStatement = function (frame) { + log += frame.environment.parent.getVariable("x") + frame.environment.parent.getVariable("y"); +}; +g.eval("var x = 'a'; this.y = 'b'; debugger;"); +assertEq(log, 'ab'); diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-06.js b/js/src/jit-test/tests/debug/Environment-getVariable-06.js new file mode 100644 index 000000000..096f77d8e --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-06.js @@ -0,0 +1,12 @@ +// getVariable sees properties inherited from the global object's prototype chain. + +var g = newGlobal(); +var dbg = Debugger(g); +var log = ''; +dbg.onDebuggerStatement = function (frame) { + log += frame.environment.parent.getVariable("x") + frame.environment.parent.getVariable("y"); +}; +g.eval("Object.getPrototypeOf(this).x = 'a';\n" + + "Object.prototype.y = 'b';\n" + + "debugger;\n"); +assertEq(log, 'ab'); diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-07.js b/js/src/jit-test/tests/debug/Environment-getVariable-07.js new file mode 100644 index 000000000..964a6db58 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-07.js @@ -0,0 +1,10 @@ +// getVariable can get properties from with-block scopes. + +var g = newGlobal(); +var dbg = Debugger(g); +var v; +dbg.onDebuggerStatement = function (frame) { + v = frame.environment.getVariable("x"); +}; +g.eval("var x = 1; { let x = 2; with ({x: 3}) { debugger; } }"); +assertEq(v, 3); diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-08.js b/js/src/jit-test/tests/debug/Environment-getVariable-08.js new file mode 100644 index 000000000..48ea9bf87 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-08.js @@ -0,0 +1,10 @@ +// getVariable sees properties inherited from a with-object's prototype chain. + +var g = newGlobal(); +var dbg = Debugger(g); +var v; +dbg.onDebuggerStatement = function (frame) { + v = frame.environment.getVariable("x"); +}; +g.eval("var x = 1; { let x = 2; with (Object.create({x: 3})) { debugger; } }"); +assertEq(v, 3); diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-09.js b/js/src/jit-test/tests/debug/Environment-getVariable-09.js new file mode 100644 index 000000000..d3155e957 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-09.js @@ -0,0 +1,13 @@ +// getVariable works on ancestors of frame.environment. + +var g = newGlobal(); +var dbg = Debugger(g); +var log = ''; +dbg.onDebuggerStatement = function (frame) { + for (var env = frame.environment; env; env = env.parent) { + if (env.find("x") === env) + log += env.getVariable("x"); + } +}; +g.eval("var x = 1; { let x = 2; with (Object.create({x: 3})) { debugger; } }"); +assertEq(log, "321"); diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-10.js b/js/src/jit-test/tests/debug/Environment-getVariable-10.js new file mode 100644 index 000000000..d79056eef --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-10.js @@ -0,0 +1,27 @@ +// getVariable works on a heavyweight environment after control leaves its scope. + +var g = newGlobal(); +var dbg = Debugger(g); +var envs = []; +dbg.onDebuggerStatement = function (frame) { + envs.push(frame.environment); +}; +g.eval("var f;\n" + + "for (var x = 0; x < 3; x++) {\n" + + " (function (x) {\n" + + " for (var y = 0; y < 3; y++) {\n" + + " (function (z) {\n" + + " eval(z); // force heavyweight\n" + + " debugger;\n" + + " })(x + y);\n" + + " }\n" + + " })(x);\n" + + "}"); + +var i = 0; +for (var x = 0; x < 3; x++) { + for (var y = 0; y < 3; y++) { + var e = envs[i++]; + assertEq(e.getVariable("z"), x + y); + } +} diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-11.js b/js/src/jit-test/tests/debug/Environment-getVariable-11.js new file mode 100644 index 000000000..ac144407e --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-11.js @@ -0,0 +1,15 @@ +// The value returned by getVariable can be a Debugger.Object. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var a = frame.environment.parent.getVariable('Math'); + assertEq(a instanceof Debugger.Object, true); + var b = gw.getOwnPropertyDescriptor('Math').value; + assertEq(a, b); + hits++; +}; +g.eval("debugger;"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-12.js b/js/src/jit-test/tests/debug/Environment-getVariable-12.js new file mode 100644 index 000000000..8005c3a73 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-12.js @@ -0,0 +1,61 @@ +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + hits++; + assertEq(frame.environment.parent.getVariable('y'), true); +}; + +g.eval("var g;" + + "function f(x) {" + + " { let y = x; " + + " if (x)" + + " g = function() { eval('debugger') };" + + " else" + + " g();" + + " }" + + "}" + + "f(true);" + + "f(false);"); +assertEq(hits, 1); + +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + hits++; + assertEq(frame.environment.parent.getVariable('y'), 1); + assertEq(frame.environment.parent.names().indexOf('z'), -1); +}; + +g.eval("var g;" + + "{ let y = 1; " + + " g = function () { debugger; };" + + " { let z = 2; " + + " g();" + + " }"+ + "}"); +assertEq(hits, 1); + +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + hits++; + var e = frame.older.environment.parent; + assertEq(e.getVariable('z'), true); + e = e.parent; + assertEq(e.getVariable('y'), true); +}; + +g.eval("var g;" + + "function h() { debugger };" + + "for (var x of [true, false]) {" + + " { let y = x; " + + " { let z = x; " + + " if (x)" + + " g = function () { print(z); h() };" + + " else" + + " g();" + + " }" + + " }" + + "}"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-13.js b/js/src/jit-test/tests/debug/Environment-getVariable-13.js new file mode 100644 index 000000000..aa8ea34b2 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-13.js @@ -0,0 +1,47 @@ +// Tests that we can use debug scopes with Ion frames. +// +// Unfortunately these tests are brittle. They depend on opaque JIT heuristics +// kicking in. + +load(libdir + "jitopts.js"); + +if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation)) + quit(0); + +withJitOptions(Opts_Ion2NoOffthreadCompilation, function () { + var g = newGlobal(); + var dbg = new Debugger; + + // Note that this *depends* on CCW scripted functions being opaque to Ion + // optimization and not deoptimizing the frames below the call to toggle. + g.toggle = function toggle(d) { + if (d) { + dbg.addDebuggee(g); + var frame = dbg.getNewestFrame(); + assertEq(frame.implementation, "ion"); + // g is heavyweight but its call object is optimized out, because its + // arguments and locals are unaliased. + // + // Calling frame.environment here should make a fake debug scope that + // gets things directly from the frame. Calling frame.arguments doesn't + // go through the scope object and reads directly off the frame. Assert + // that the two are equal. + assertEq(frame.environment.getVariable("x"), frame.arguments[1]); + } + }; + + g.eval("" + function f(d, x) { g(d, x); }); + g.eval("" + function g(d, x) { + for (var i = 0; i < 200; i++); + function inner() { i = 42; }; + toggle(d); + // Use x so it doesn't get optimized out. + x++; + }); + + g.eval("(" + function test() { + for (i = 0; i < 5; i++) + f(false, 42); + f(true, 42); + } + ")();"); +}); diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-14.js b/js/src/jit-test/tests/debug/Environment-getVariable-14.js new file mode 100644 index 000000000..237ea6e1a --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-14.js @@ -0,0 +1,18 @@ +// Debugger.Environment can reflect optimized out function scopes + +var g = newGlobal(); +var dbg = new Debugger; +dbg.addDebuggee(g); + +g.eval("" + function f() { + var x = 42; + function g() { } + g(); +}); + +dbg.onEnterFrame = function (f) { + if (f.callee && (f.callee.name === "g")) + assertEq(f.environment.parent.getVariable("x").optimizedOut, true); +} + +g.f(); diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-15.js b/js/src/jit-test/tests/debug/Environment-getVariable-15.js new file mode 100644 index 000000000..b6eee90e6 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-15.js @@ -0,0 +1,31 @@ +// Don't hand out internal function objects via Debugger.Environment.prototype.getVariable. + +// When the real scope chain object holding the binding for 'f' in 'function f() +// { ... }' is optimized out because it's never used, we whip up fake scope +// chain objects for Debugger to use, if it looks. However, the value of the +// variable f will be an internal function object, not a live function object, +// since the latter was not recorded. Internal function objects should not be +// exposed via Debugger. + +var g = newGlobal(); +var dbg = new Debugger(g); + +dbg.onDebuggerStatement = function (frame) { + var g_call_env = frame.older.environment; // g's locals + var g_decl_env = g_call_env.parent; // 'function g' binding + var f_call_env = g_decl_env.parent; // f's locals + var f_decl_env = f_call_env.parent; // 'function f' binding + assertEq(f_decl_env.getVariable('f').optimizedOut, true); +} + +g.evaluate(` + + function h() { debugger; } + (function f() { + return function g() { + h(); + return 1; + } + })()(); + + `); diff --git a/js/src/jit-test/tests/debug/Environment-getVariable-WouldRun.js b/js/src/jit-test/tests/debug/Environment-getVariable-WouldRun.js new file mode 100644 index 000000000..495399985 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-getVariable-WouldRun.js @@ -0,0 +1,17 @@ +// getVariable that would trigger a getter does not crash or explode. +// It should throw WouldRunDebuggee, but that isn't implemented yet. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + assertThrowsInstanceOf(function () { + frame.environment.parent.parent.getVariable("x"); + }, Error); + hits++; +}; +g.eval("Object.defineProperty(this, 'x', {get: function () { throw new Error('fail'); }});\n" + + "debugger;"); +assertEq(hits, 1); 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 000000000..fe9f9c620 --- /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(); +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 000000000..ed576bf37 --- /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(); +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 000000000..fe5dcc7c0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-identity-03.js @@ -0,0 +1,106 @@ +// 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(); +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, "let 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);"); + +// 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" + [...range(0, s)].map((i) => "(" + i + ")").join("") + ";\n" + + "mid" + [...range(s, N)].map((i) => "(" + i + ")").join("") + ";\n" + + "mid" + [...range(s, N)].map((i) => "(" + i + ")").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 000000000..bf9b4086d --- /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(); +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-identity-05.js b/js/src/jit-test/tests/debug/Environment-identity-05.js new file mode 100644 index 000000000..c1d06b7e7 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-identity-05.js @@ -0,0 +1,19 @@ +// Tests that freshened blocks behave correctly in Debugger. + +var g = newGlobal(); +var dbg = Debugger(g); +var log = ''; +var oldEnv = null; +dbg.onDebuggerStatement = function (frame) { + if (!oldEnv) { + oldEnv = frame.environment; + } else { + // Block has been freshened by |for (let ...)|, should be different + // identity. + log += (oldEnv === frame.environment); + } + log += frame.environment.getVariable("x"); +}; +g.eval("for (let x = 0; x < 2; x++) debugger;"); +gc(); +assertEq(log, "0false1"); diff --git a/js/src/jit-test/tests/debug/Environment-inspectable-01.js b/js/src/jit-test/tests/debug/Environment-inspectable-01.js new file mode 100644 index 000000000..ee5897833 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-inspectable-01.js @@ -0,0 +1,80 @@ +// Environments are only inspectable while their globals are debuggees. + +load(libdir + 'asserts.js'); + +var g1 = newGlobal(); +var g2 = newGlobal(); +g2.g1 = g1; +g1.g2 = g2; + +g1.eval('function f(xf) { return function h(xh) { debugger; } }'); +g1.eval('var h = f("value of xf");'); + +// To ensure that xk gets located on the heap, and thus outlives its stack frame, we +// store a function that captures it here. Kind of a kludge. +g2.eval('var capture;'); +g2.eval('function k(xk) { capture = function () { return xk; }; g1.h("value of xh"); }'); + +var dbg = new Debugger; +dbg.addDebuggee(g1); +dbg.addDebuggee(g2); + +dbg.onDebuggerStatement = debuggerHandler; + +var log = ''; + +g1.eval('g2.k("value of xk");'); + +var he, ke, ee; + +function debuggerHandler(frame) { + log += 'd'; + + assertEq(frame.type, 'call'); + he = frame.environment; + + assertEq(frame.older.type, 'call'); + ke = frame.older.environment; + + assertEq(frame.older.older.type, 'eval'); + ee = frame.older.older.environment; + + assertEq(he.inspectable, true); + assertEq(he.getVariable('xh'), 'value of xh'); + assertEq(he.parent.parent.getVariable('xf'), 'value of xf'); + assertEq(ke.inspectable, true); + assertEq(ke.getVariable('xk'), 'value of xk'); + assertEq(ee.inspectable, true); + assertEq(ee.type, 'declarative'); + assertEq(ee.parent.type, 'object'); + + dbg.removeDebuggee(g2); + + assertEq(he.inspectable, true); + assertEq(he.type, 'declarative'); + assertEq(ke.inspectable, false); + assertThrowsInstanceOf(() => ke.getVariable('xk'), Error); + assertEq(ee.inspectable, true); + assertEq(ee.type, 'declarative'); + assertEq(ee.parent.type, 'object'); + + dbg.removeDebuggee(g1); + + assertEq(he.inspectable, false); + assertThrowsInstanceOf(() => he.getVariable('xh'), Error); + assertEq(ke.inspectable, false); + assertThrowsInstanceOf(() => ke.getVariable('xk'), Error); + assertEq(ee.inspectable, false); + assertThrowsInstanceOf(() => ee.type, Error); +} + +assertEq(log, 'd'); + +dbg.addDebuggee(g2); + +assertEq(he.inspectable, false); +assertThrowsInstanceOf(() => he.getVariable('xh'), Error); +assertEq(ke.inspectable, true); +assertEq(ke.getVariable('xk'), 'value of xk'); +assertEq(ee.inspectable, false); +assertThrowsInstanceOf(() => ee.type, Error); 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 000000000..b58cbb0d3 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-names-01.js @@ -0,0 +1,19 @@ +// env.names() lists nonenumerable names in with-statement environments. + +var g = newGlobal(); +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); + + // FIXME: Bug 748592 - proxies don't correctly propagate JSITER_HIDDEN + //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 000000000..982043f63 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-names-02.js @@ -0,0 +1,34 @@ +// env.names() on object environments ignores property names that are not identifiers. + +var g = newGlobal(); +var dbg = Debugger(g); +var withNames, globalNames; +g.h = function () { + var env = dbg.getNewestFrame().environment; + withNames = env.names(); + while (env.parent !== null) + env = env.parent; + globalNames = env.names(); +}; + +g.eval("" + + function fill(obj) { + obj.sanityCheck = 1; + obj["0xcafe"] = 2; + obj[" "] = 3; + obj[""] = 4; + obj[0] = 5; + obj[Symbol.for("moon")] = 6; + return obj; + }) +g.eval("fill(this);\n" + + "with (fill({})) h();"); + +for (var names of [withNames, globalNames]) { + assertEq(names.indexOf("sanityCheck") !== -1, true); + assertEq(names.indexOf("0xcafe"), -1); + assertEq(names.indexOf(" "), -1); + assertEq(names.indexOf(""), -1); + assertEq(names.indexOf("0"), -1); + assertEq(names.indexOf(Symbol.for("moon")), -1); +} diff --git a/js/src/jit-test/tests/debug/Environment-names-03.js b/js/src/jit-test/tests/debug/Environment-names-03.js new file mode 100644 index 000000000..a6275cc3b --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-names-03.js @@ -0,0 +1,22 @@ +// Optimized out scopes should have working names(). + +var g = newGlobal(); +var dbg = new Debugger; +dbg.addDebuggee(g); + +g.eval("" + function f() { + var x = 42; + function g() { } + g(); +}); + +dbg.onEnterFrame = function (f) { + if (f.callee && (f.callee.name === "g")) { + var names = f.environment.parent.names(); + assertEq(names.indexOf("x") !== -1, true); + assertEq(names.indexOf("g") !== -1, true); + assertEq(names.length, 3); // x,g,arguments + } +} + +g.f(); diff --git a/js/src/jit-test/tests/debug/Environment-nondebuggee.js b/js/src/jit-test/tests/debug/Environment-nondebuggee.js new file mode 100644 index 000000000..72d4ae990 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-nondebuggee.js @@ -0,0 +1,40 @@ +// Using an environment that is not a debuggee should throw. + +load(libdir + 'asserts.js'); + +var g = newGlobal(); +var dbg = new Debugger; +let gw = dbg.addDebuggee(g); +var log; + +function check(env) { + assertEq(env.inspectable, false); + assertThrowsInstanceOf(() => env.type, Error); + assertThrowsInstanceOf(() => env.object, Error); + assertThrowsInstanceOf(() => env.parent, Error); + assertThrowsInstanceOf(() => env.callee, Error); + + assertThrowsInstanceOf(() => env.names(), Error); + assertThrowsInstanceOf(() => env.find('x'), Error); + assertThrowsInstanceOf(() => env.getVariable('x'), Error); + assertThrowsInstanceOf(() => env.setVariable('x'), Error); +} + +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + + let env = frame.environment; + dbg.removeDebuggee(g); + check(env); +} + +log = ''; +g.eval('let x = 42; debugger;'); +assertEq(log, 'd'); + +dbg.addDebuggee(g); +g.eval('function f() { }'); +let env = gw.getOwnPropertyDescriptor('f').value.environment; +assertEq(env.type, 'declarative'); +dbg.removeDebuggee(g); +check(env); diff --git a/js/src/jit-test/tests/debug/Environment-object-01.js b/js/src/jit-test/tests/debug/Environment-object-01.js new file mode 100644 index 000000000..3ef1702a0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-object-01.js @@ -0,0 +1,8 @@ +var g = newGlobal(); +var dbg = new Debugger(g); + +dbg.onDebuggerStatement = (frame) => { + assertEq(frame.environment.parent.type, "object"); + assertEq(frame.environment.parent.object.getOwnPropertyDescriptor("x").value, 42); +} +g.evalReturningScope("x = 42; debugger;"); diff --git a/js/src/jit-test/tests/debug/Environment-optimizedOut-01.js b/js/src/jit-test/tests/debug/Environment-optimizedOut-01.js new file mode 100644 index 000000000..7701ebc07 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-optimizedOut-01.js @@ -0,0 +1,44 @@ +// Optimized out scopes should be considered optimizedOut. + +var g = newGlobal(); +var dbg = new Debugger; +dbg.addDebuggee(g); + +g.eval("" + function f() { + var x = 42; + { + let y = 43; + (function () { })(); + } +}); + +dbg.onEnterFrame = function (f) { + if (f.callee && (f.callee.name === undefined)) { + blockenv = f.environment.parent; + assertEq(blockenv.optimizedOut, true); + assertEq(blockenv.inspectable, true); + assertEq(blockenv.type, "declarative"); + assertEq(blockenv.callee, null); + assertEq(blockenv.names().indexOf("y") !== -1, true); + + funenv = blockenv.parent; + assertEq(funenv.optimizedOut, true); + assertEq(funenv.inspectable, true); + assertEq(funenv.type, "declarative"); + assertEq(funenv.callee, f.older.callee); + assertEq(funenv.names().indexOf("x") !== -1, true); + + globalenv = funenv.parent.parent; + assertEq(globalenv.optimizedOut, false); + assertEq(globalenv.inspectable, true); + assertEq(globalenv.type, "object"); + assertEq(globalenv.callee, null); + + dbg.removeDebuggee(g); + + assertEq(blockenv.inspectable, false); + assertEq(funenv.inspectable, false); + } +} + +g.f(); 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 000000000..07e61faed --- /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() +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-setVariable-01.js b/js/src/jit-test/tests/debug/Environment-setVariable-01.js new file mode 100644 index 000000000..610960241 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-setVariable-01.js @@ -0,0 +1,9 @@ +// Environment.prototype.setVariable can set global variables. + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + frame.environment.parent.setVariable("x", 2); +}; +g.eval("var x = 1; debugger;"); +assertEq(g.x, 2); diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-02.js b/js/src/jit-test/tests/debug/Environment-setVariable-02.js new file mode 100644 index 000000000..02494609e --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-setVariable-02.js @@ -0,0 +1,10 @@ +// The argument to setVariable can be a Debugger.Object. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +dbg.onDebuggerStatement = function (frame) { + frame.environment.parent.setVariable("x", gw); +}; +g.eval("var x = 1; debugger;"); +assertEq(g.x, g); diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-03.js b/js/src/jit-test/tests/debug/Environment-setVariable-03.js new file mode 100644 index 000000000..752f8122e --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-setVariable-03.js @@ -0,0 +1,16 @@ +// setVariable cannot create new global variables. +// (Other kinds of environment are tested in Environment-variables.js.) + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + assertThrowsInstanceOf(function () { frame.environment.setVariable("x", 7); }, TypeError); + hits++; +}; +g.eval("debugger"); +assertEq("x" in g, false); +assertEq(hits, 1); + diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-04.js b/js/src/jit-test/tests/debug/Environment-setVariable-04.js new file mode 100644 index 000000000..9b3b32025 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-setVariable-04.js @@ -0,0 +1,10 @@ +// setVariable can set variables and arguments in functions. + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onDebuggerStatement = function (frame) { + frame.environment.setVariable("a", 100); + frame.environment.setVariable("b", 200); +}; +g.eval("function f(a) { var b = a + 1; debugger; return a + b; }"); +assertEq(g.f(1), 300); diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-05.js b/js/src/jit-test/tests/debug/Environment-setVariable-05.js new file mode 100644 index 000000000..0d5e6aec6 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-setVariable-05.js @@ -0,0 +1,14 @@ +// setVariable can change the types of variables and arguments in functions. + +var g = newGlobal(); +g.eval("function f(a) { var b = a + 1; debugger; return a + b; }"); +for (var i = 0; i < 20; i++) + assertEq(g.f(i), 2 * i + 1); + +var dbg = new Debugger(g); +dbg.onDebuggerStatement = function (frame) { + frame.environment.setVariable("a", "xyz"); + frame.environment.setVariable("b", "zy"); +}; +for (var i = 0; i < 10; i++) + assertEq(g.f(i), "xyzzy"); diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-06.js b/js/src/jit-test/tests/debug/Environment-setVariable-06.js new file mode 100644 index 000000000..17d9f8eb0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-setVariable-06.js @@ -0,0 +1,9 @@ +// setVariable on an argument works as expected with non-strict 'arguments'. + +var g = newGlobal(); +g.eval("function f(a) { debugger; return arguments[0]; }"); +var dbg = new Debugger(g); +dbg.onDebuggerStatement = function (frame) { + frame.environment.setVariable("a", 2); +}; +assertEq(g.f(1), 2); diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-07.js b/js/src/jit-test/tests/debug/Environment-setVariable-07.js new file mode 100644 index 000000000..5e3cdd177 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-setVariable-07.js @@ -0,0 +1,14 @@ +// setVariable works on let-bindings. + +var g = newGlobal(); +function test(code, val) { + g.eval("function f() { " + code + " }"); + var dbg = new Debugger(g); + dbg.onDebuggerStatement = function (frame) { + frame.environment.setVariable("a", val); + }; + assertEq(g.f(), val); +} + +test("let a = 1; debugger; return a;", "xyzzy"); +test("{ let a = 1; debugger; return a; }", "plugh"); diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-08.js b/js/src/jit-test/tests/debug/Environment-setVariable-08.js new file mode 100644 index 000000000..014483c4a --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-setVariable-08.js @@ -0,0 +1,29 @@ +// setVariable throws if no binding exists. + +load(libdir + "asserts.js"); + +function test(code) { + var g = newGlobal(); + var dbg = new Debugger(g); + var hits = 0; + dbg.onDebuggerStatement = function (frame) { + var env = frame.older.environment; + assertThrowsInstanceOf(function () { env.setVariable("y", 2); }, Error); + hits++; + }; + g.eval("var y = 0; function d() { debugger; }"); + + assertEq(g.eval(code), 0); + + assertEq(g.y, 0); + assertEq(hits, 1); +} + +// local scope of non-heavyweight function +test("function f() { var x = 1; d(); return y; } f();"); + +// block scope +test("function h(x) { if (x) { let x = 1; d(); return y; } } h(3);"); + +// strict eval scope +test("'use strict'; eval('d(); y;');"); diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-10.js b/js/src/jit-test/tests/debug/Environment-setVariable-10.js new file mode 100644 index 000000000..6247d1fdf --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-setVariable-10.js @@ -0,0 +1,32 @@ +// setVariable works on non-innermost environments. + +// (The debuggee code here is a bit convoluted to defeat optimizations that +// could make obj.b a null closure or obj.i a flat closure--that is, a function +// that gets a frozen copy of i instead of a reference to the runtime +// environment that contains it. setVariable does not currently detect this +// flat closure case.) + +var g = newGlobal(); +g.eval("function d() { debugger; }\n" + + "var i = 'FAIL';\n" + + "function a() {\n" + + " var obj = {b: function (i) { d(obj); return i; },\n" + + " i: function () { return i; }};\n" + + " var i = 'FAIL2';\n" + + " return obj;\n" + + "}\n"); + +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + var x = 0; + for (var env = frame.older.environment; env; env = env.parent) { + if (env.getVariable("i") !== undefined) + env.setVariable("i", x++); + } +}; + +var obj = g.a(); +var r = obj.b('FAIL3'); +assertEq(r, 0); +assertEq(obj.i(), 1); +assertEq(g.i, 2); diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-11.js b/js/src/jit-test/tests/debug/Environment-setVariable-11.js new file mode 100644 index 000000000..6491468e2 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-setVariable-11.js @@ -0,0 +1,16 @@ +// setVariable cannot modify the binding for a FunctionExpression's name. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var env = frame.environment.find("f"); + assertEq(env.getVariable("f"), frame.callee); + assertThrowsInstanceOf(function () { env.setVariable("f", 0) }, TypeError); + assertThrowsInstanceOf(function () { env.setVariable("f", frame.callee) }, TypeError); + hits++; +}; +g.eval("(function f() { debugger; })();"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-12.js b/js/src/jit-test/tests/debug/Environment-setVariable-12.js new file mode 100644 index 000000000..2fd3b6fbc --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-setVariable-12.js @@ -0,0 +1,21 @@ +// setVariable can create a new property on a with block's bindings object, if +// it is shadowing an existing property on the prototype chain. + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + var env = frame.environment.find("x"); + env.setVariable("x", 2); +}; +g.eval("var obj1 = {x: 1}, obj2 = Object.create(obj1), z; with (obj2) { debugger; z = x; }"); +assertEq(g.obj1.x, 1); +assertEq(g.obj2.x, 2); +assertEq(g.z, 2); + +// The property created by setVariable is like the one created by ordinary +// assignment in a with-block. +var desc = Object.getOwnPropertyDescriptor(g.obj2, "x"); +assertEq(desc.configurable, true); +assertEq(desc.enumerable, true); +assertEq(desc.writable, true); +assertEq(desc.value, 2); diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-13.js b/js/src/jit-test/tests/debug/Environment-setVariable-13.js new file mode 100644 index 000000000..a9e51ca6b --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-setVariable-13.js @@ -0,0 +1,20 @@ +// Debugger.Environment should throw trying to setVariable on optimized out scope. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = new Debugger; +dbg.addDebuggee(g); + +g.eval("" + function f() { + var x = 42; + function g() { } + g(); +}); + +dbg.onEnterFrame = function (f) { + if (f.callee && (f.callee.name === "g")) + assertThrowsInstanceOf(function () { f.environment.parent.setVariable("x", 43) }, ReferenceError); +} + +g.f(); diff --git a/js/src/jit-test/tests/debug/Environment-setVariable-WouldRun.js b/js/src/jit-test/tests/debug/Environment-setVariable-WouldRun.js new file mode 100644 index 000000000..b19a58e81 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-setVariable-WouldRun.js @@ -0,0 +1,22 @@ +// setVariable triggering a setter doesn't crash or explode. +// It should throw WouldRunDebuggee, but that isn't implemented yet. + +function test(code) { + var g = newGlobal(); + g.eval("function d() { debugger; }"); + var dbg = Debugger(g); + var hits = 0; + dbg.onDebuggerStatement = function (frame) { + var env = frame.environment.find("x"); + try { + env.setVariable("x", 0); + } catch (exc) { + } + hits++; + }; + g.eval(code); +} + +test("Object.defineProperty(this, 'x', {set: function (v) {}}); d();"); +test("Object.defineProperty(Object.prototype, 'x', {set: function (v) {}}); d();"); +test("with ({set x(v) {}}) eval(d());"); 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 000000000..35489840c --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-type-01.js @@ -0,0 +1,29 @@ +// env.type is 'object' in global environments and with-blocks, and 'declarative' otherwise. + +var g = newGlobal(); +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();", 'declarative'); +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();", 'with'); +test("(function (s) { with ({x: 1, y: 2}) h(); })();", 'with'); +test("{ let x = 1; h(); }", 'declarative'); +test("for (let x = 0; x < 1; x++) h();", 'declarative'); +test("for (let x in h()) ;", 'declarative'); +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'); + +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.eval("h(), 2 + 2;").return, 4); +} +test("debugger;", 'declarative'); +test("(function f() { debugger; })();", 'declarative'); diff --git a/js/src/jit-test/tests/debug/Environment-unscopables.js b/js/src/jit-test/tests/debug/Environment-unscopables.js new file mode 100644 index 000000000..0075f5072 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-unscopables.js @@ -0,0 +1,37 @@ +// An Environment for a `with` statement does not observe bindings ruled out by @@unscopables. + +load(libdir + "asserts.js"); + +let g = newGlobal(); +g.eval(` + let x = 'global'; + function f() { + let obj = { + x: 'obj', + y: 'obj', + [Symbol.unscopables]: {x: 1}, + }; + with (obj) + debugger; + } +`); +let dbg = Debugger(g); +let hits = 0; +dbg.onDebuggerStatement = function (frame) { + let env = frame.environment; + + assertEq(env.find("x") !== env, true); + assertEq(env.names().indexOf("x"), -1); + assertEq(env.getVariable("x"), undefined); + assertThrowsInstanceOf(() => env.setVariable("x", 7), TypeError); + + assertEq(env.find("y") === env, true); + assertEq(env.getVariable("y"), "obj"); + env.setVariable("y", 8); + + assertEq(frame.eval("x").return, "global"); + assertEq(frame.eval("y").return, 8); + hits++; +}; +g.f(); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Environment-variables.js b/js/src/jit-test/tests/debug/Environment-variables.js new file mode 100644 index 000000000..f7fe35c01 --- /dev/null +++ b/js/src/jit-test/tests/debug/Environment-variables.js @@ -0,0 +1,86 @@ +// Comprehensive test of get/setVariable on many kinds of environments and +// bindings. + +load(libdir + "asserts.js"); + +var cases = [ + // global bindings and bindings on the global prototype chain + "x = VAL; @@", + "var x = VAL; @@", + "Object.prototype.x = VAL; @@", + + // let, catch, and comprehension bindings + "let x = VAL; @@", + "{ let x = VAL; @@ }", + "try { throw VAL; } catch (x) { @@ }", + "try { throw VAL; } catch (x) { @@ }", + "for (let x of [VAL]) { @@ }", + "for each (let x in [VAL]) { @@ }", + "switch (0) { default: let x = VAL; @@ }", + + // arguments + "function f(x) { @@ } f(VAL);", + "function f([w, x]) { @@ } f([0, VAL]);", + "function f({v: x}) { @@ } f({v: VAL});", + "function f([w, {v: x}]) { @@ } f([0, {v: VAL}]);", + + // bindings in functions + "function f() { var x = VAL; @@ } f();", + "function f() { let x = VAL; @@ } f();", + "function f() { function x() {} x = VAL; @@ } f();", + + // dynamic bindings + "function f(s) { eval(s); @@ } f('var x = VAL');", + "var x = VAL; function f(s) { eval('var x = 0;'); eval(s); @@ } f('delete x;');", + "function f(obj) { with (obj) { @@ } } f({x: VAL});", + "function f(obj) { with (obj) { @@ } } f(Object.create({x: VAL}));", + "function f(b) { if (b) { function x(){} } x = VAL; @@ } f(1);", +]; + +var nextval = 1000; + +function test(code, debugStmts, followupStmts) { + var val = nextval++; + var hits = 0; + + var g = newGlobal(); + g.eval("function debugMe() { var x = 'wrong-x'; debugger; }"); + g.capture = null; + + var dbg = Debugger(g); + dbg.onDebuggerStatement = function (frame) { + if (frame.callee !== null && frame.callee.name == 'debugMe') + frame = frame.older; + var env = frame.environment.find("x"); + assertEq(env.getVariable("x"), val) + assertEq(env.setVariable("x", 'ok'), undefined); + assertEq(env.getVariable("x"), 'ok'); + + // setVariable cannot create new variables. + assertThrowsInstanceOf(function () { env.setVariable("newVar", 0); }, TypeError); + hits++; + }; + + code = code.replace("@@", debugStmts); + if (followupStmts !== undefined) + code += " " + followupStmts; + code = code.replace(/VAL/g, uneval(val)); + g.eval(code); + assertEq(hits, 1); +} + +for (var s of cases) { + // Test triggering the debugger right in the scope in which x is bound. + test(s, "debugger; assertEq(x, 'ok');"); + + // Test calling a function that triggers the debugger. + test(s, "debugMe(); assertEq(x, 'ok');"); + + // Test triggering the debugger from a scope nested in x's scope. + test(s, "{ let y = 'irrelevant'; (function (z) { { let zz = y; debugger; } })(); } assertEq(x, 'ok');"), + + // Test closing over the variable and triggering the debugger later, after + // leaving the variable's scope. + test(s, "capture = {dbg: function () { debugger; }, get x() { return x; }};", + "assertEq(capture.x, VAL); capture.dbg(); assertEq(capture.x, 'ok');"); +} diff --git a/js/src/jit-test/tests/debug/Frame-01.js b/js/src/jit-test/tests/debug/Frame-01.js new file mode 100644 index 000000000..cfcbe5a30 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-01.js @@ -0,0 +1,34 @@ +// Test .type and .generator fields of topmost stack frame passed to onDebuggerStatement. + +var g = newGlobal(); +var dbg = Debugger(g); +var expected, hits; +dbg.onDebuggerStatement = function (f) { + assertEq(Object.getPrototypeOf(f), Debugger.Frame.prototype); + assertEq(f.type, expected.type); + assertEq(f.generator, expected.generator); + assertEq(f.constructing, expected.constructing); + hits++; +}; + +function test(code, expectobj, expectedHits) { + expected = expectobj; + hits = 0; + g.evaluate(code); + assertEq(hits, arguments.length < 3 ? 1 : expectedHits); +} + +test("debugger;", {type: "global", generator: false, constructing: false}); +test("(function () { debugger; })();", {type: "call", generator: false, constructing: false}); +test("new function() { debugger; };", {type: "call", generator: false, constructing: true}); +test("new function () { (function() { debugger; })(); }", {type: "call", generator: false, constructing: false}); +test("eval('debugger;');", {type: "eval", generator: false, constructing: false}); +test("this.eval('debugger;'); // indirect eval", {type: "eval", generator: false, constructing: false}); +test("(function () { eval('debugger;'); })();", {type: "eval", generator: false, constructing: false}); +test("new function () { eval('debugger'); }", {type: "eval", generator: false, constructing: false}); +test("function gen() { debugger; yield 1; debugger; }\n" + + "for (var x in gen()) {}\n", + {type: "call", generator: true, constructing: false}, 2); +test("var iter = (function* stargen() { debugger; yield 1; debugger; })();\n" + + "iter.next(); iter.next();", + {type: "call", generator: true, constructing: false}, 2); diff --git a/js/src/jit-test/tests/debug/Frame-02.js b/js/src/jit-test/tests/debug/Frame-02.js new file mode 100644 index 000000000..bb569aca4 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-02.js @@ -0,0 +1,24 @@ +// When the debugger is triggered twice from the same stack frame, the same +// Debugger.Frame object is passed to the hook both times. + +var g = newGlobal(); +var hits, frame; +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (f) { + if (hits++ == 0) + frame = f; + else + assertEq(f, frame); +}; + +hits = 0; +g.evaluate("debugger; debugger;"); +assertEq(hits, 2); + +hits = 0; +g.evaluate("function f() { debugger; debugger; } f();"); +assertEq(hits, 2); + +hits = 0; +g.evaluate("eval('debugger; debugger;');"); +assertEq(hits, 2); diff --git a/js/src/jit-test/tests/debug/Frame-03.js b/js/src/jit-test/tests/debug/Frame-03.js new file mode 100644 index 000000000..64463ab4e --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-03.js @@ -0,0 +1,19 @@ +// When the debugger is triggered from different stack frames that happen to +// occupy the same memory, it delivers different Debugger.Frame objects. + +var g = newGlobal(); +var dbg = Debugger(g); +var hits; +var a = []; +dbg.onDebuggerStatement = function (frame) { + for (var i = 0; i < a.length; i++) + assertEq(a[i] === frame, false); + a.push(frame); + hits++; +}; + +g.eval("function f() { debugger; }"); +g.eval("function h() { debugger; f(); }"); +hits = 0; +g.eval("for (var i = 0; i < 4; i++) h();"); +assertEq(hits, 8); diff --git a/js/src/jit-test/tests/debug/Frame-arguments-01.js b/js/src/jit-test/tests/debug/Frame-arguments-01.js new file mode 100644 index 000000000..9cf6b7dfd --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-arguments-01.js @@ -0,0 +1,41 @@ +// Frame.prototype.arguments with primitive values + +var g = newGlobal(); +g.args = null; +var dbg = new Debugger(g); +var hits; +var v; +dbg.onDebuggerStatement = function (frame) { + hits++; + var args = frame.arguments; + assertEq(args instanceof Array, true); + assertEq(Array.isArray(args), false); + assertEq(args, frame.arguments); + assertEq(args.length, g.args.length); + for (var i = 0; i < args.length; i++) + assertEq(args[i], g.args[i]); +}; + +// no formal parameters +g.eval("function f() { debugger; }"); + +hits = 0; +g.eval("args = []; f();"); +g.eval("this.f();"); +g.eval("var world = Symbol('world'); " + + "args = ['hello', world, 3.14, true, false, null, undefined]; " + + "f('hello', world, 3.14, true, false, null, undefined);"); +g.eval("f.apply(undefined, args);"); +g.eval("args = [-0, NaN, -1/0]; this.f(-0, NaN, -1/0);"); +assertEq(hits, 5); + +// with formal parameters +g.eval("function f(a, b) { debugger; }"); + +hits = 0; +g.eval("args = []; f();"); +g.eval("this.f();"); +g.eval("args = ['a', 'b']; f('a', 'b');"); +g.eval("this.f('a', 'b');"); +g.eval("f.bind(null, 'a')('b');"); +assertEq(hits, 5); diff --git a/js/src/jit-test/tests/debug/Frame-arguments-02.js b/js/src/jit-test/tests/debug/Frame-arguments-02.js new file mode 100644 index 000000000..697ccccbf --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-arguments-02.js @@ -0,0 +1,19 @@ +// Object arguments. + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var args = frame.arguments; + assertEq(args, frame.arguments); + assertEq(args instanceof Array, true); + assertEq(args.length, 2); + assertEq(args[0] instanceof Debugger.Object, true); + assertEq(args[0].class, args[1]); + hits++; +}; + +g.eval("function f(obj, cls) { debugger; }"); +g.eval("f({}, 'Object');"); +g.eval("f(Date, 'Function');"); +assertEq(hits, 2); diff --git a/js/src/jit-test/tests/debug/Frame-arguments-03.js b/js/src/jit-test/tests/debug/Frame-arguments-03.js new file mode 100644 index 000000000..579bc808c --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-arguments-03.js @@ -0,0 +1,34 @@ +// Destructuring arguments. + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var args = frame.arguments; + assertEq(args[0], 1); + assertEq(args.length, 4); + + assertEq(args[1] instanceof Debugger.Object, true); + assertEq(args[1].class, "Array"); + var getprop = frame.eval("(function (p) { return this[p]; })").return; + assertEq(getprop instanceof Debugger.Object, true); + assertEq(getprop.apply(args[1], ["length"]).return, 2); + assertEq(getprop.apply(args[1], [0]).return, 2); + assertEq(getprop.apply(args[1], [1]).return, 3); + + assertEq(args[2] instanceof Debugger.Object, true); + assertEq(args[2].class, "Object"); + var x = getprop.apply(args[2], ["x"]).return; + assertEq(x.class, "Array"); + assertEq(getprop.apply(x, ["0"]).return, 4); + assertEq(getprop.apply(args[2], ["z"]).return, 5); + + assertEq(args[3] instanceof Debugger.Object, true); + assertEq(args[3].class, "Object"); + assertEq(getprop.apply(args[3], ["q"]).return, 6); + hits++; +}; + +g.eval("function f(a, [b, c], {x: [y], z: w}, {q}) { debugger; }"); +g.eval("f(1, [2, 3], {x: [4], z: 5}, {q: 6});"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Frame-arguments-04.js b/js/src/jit-test/tests/debug/Frame-arguments-04.js new file mode 100644 index 000000000..c8d13afee --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-arguments-04.js @@ -0,0 +1,18 @@ +// frame.arguments works for all live frames + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + for (var i = 0; i <= 4; i++) { + assertEq(frame.arguments.length, 1); + assertEq(frame.arguments[0], i); + frame = frame.older; + } + assertEq(frame, null); + hits++; +}; + +g.eval("function f(n) { if (n == 0) debugger; else f(n - 1); }"); +g.f(4); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Frame-arguments-05.js b/js/src/jit-test/tests/debug/Frame-arguments-05.js new file mode 100644 index 000000000..89dd11daa --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-arguments-05.js @@ -0,0 +1,19 @@ +// frame.arguments is "live" (it reflects assignments to arguments). + +var g = newGlobal(); +var dbg = new Debugger(g); +var log = ''; +var args; +dbg.onDebuggerStatement = function (frame) { + if (args === undefined) + args = frame.arguments; + else + assertEq(frame.arguments, args); + log += args[0]; + assertEq(frame.eval("x = '0';").return, '0'); + log += args[0]; +}; + +g.eval("function f(x) { x = '2'; debugger; x = '3'; debugger; }"); +g.f("1"); +assertEq(log, "2030"); diff --git a/js/src/jit-test/tests/debug/Frame-arguments-06.js b/js/src/jit-test/tests/debug/Frame-arguments-06.js new file mode 100644 index 000000000..3ce6f4695 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-arguments-06.js @@ -0,0 +1,38 @@ +// Test extracting frame.arguments element getters and calling them in +// various awkward ways. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +var fframe, farguments, fgetter; +dbg.onDebuggerStatement = function (frame) { + if (hits === 0) { + fframe = frame; + farguments = frame.arguments; + fgetter = Object.getOwnPropertyDescriptor(farguments, "0").get; + assertEq(fgetter instanceof Function, true); + + // Calling the getter without an appropriate this-object + // fails, but shouldn't assert or crash. + assertThrowsInstanceOf(function () { fgetter.call(Math); }, TypeError); + } else { + // Since fframe is still on the stack, fgetter can be applied to it. + assertEq(fframe.live, true); + assertEq(fgetter.call(farguments), 100); + + // Since h was called without arguments, there is no argument 0. + assertEq(fgetter.call(frame.arguments), undefined); + } + hits++; +}; + +g.eval("function h() { debugger; }"); +g.eval("function f(x) { debugger; h(); }"); +g.f(100); +assertEq(hits, 2); + +// Now that fframe is no longer live, trying to get its arguments should throw. +assertEq(fframe.live, false); +assertThrowsInstanceOf(function () { fgetter.call(farguments); }, Error); diff --git a/js/src/jit-test/tests/debug/Frame-arguments-07.js b/js/src/jit-test/tests/debug/Frame-arguments-07.js new file mode 100644 index 000000000..29d6d975d --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-arguments-07.js @@ -0,0 +1,23 @@ +// When argument[x] is assigned, where x > callee.length, frame.arguments reflects the change. + +var g = newGlobal(); +g.eval("function f(a, b) {\n" + + " for (var i = 0; i < arguments.length; i++)\n" + + " arguments[i] = i;\n" + + " debugger;\n" + + "}\n"); + +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var argc = frame.eval("arguments.length").return; + var args = frame.arguments; + assertEq(args.length, argc); + for (var i = 0; i < argc; i++) + assertEq(args[i], i); + hits++; +} + +g.f(9); +g.f(9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9); +assertEq(hits, 2); 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 000000000..755885791 --- /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() +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 000000000..6d6101c59 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-environment-02.js @@ -0,0 +1,12 @@ +// dbg.getNewestFrame().environment works. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +g.h = function () { + var env = dbg.getNewestFrame().environment.parent; + 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 000000000..3676389e8 --- /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(); +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 000000000..d74e9f2b9 --- /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(); +g.eval("function f(x) { return 2 * x; }"); +var dbg = Debugger(g); +var hits = 0; +dbg.onEnterFrame = function (frame) { + assertEq(frame.environment.names().join(","), "arguments,x"); + hits++; +}; +assertEq(g.f(22), 44); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Frame-environment-05.js b/js/src/jit-test/tests/debug/Frame-environment-05.js new file mode 100644 index 000000000..98f1c9a12 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-environment-05.js @@ -0,0 +1,9 @@ +// Test that Debugger.Frame.prototype.environment works at all pcs of a script +// with an aliased block scope. + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onDebuggerStatement = function (frame) { + frame.onStep = (function () { frame.environment; }); +}; +g.eval("debugger; for (let i of [1,2,3]) print(i);"); diff --git a/js/src/jit-test/tests/debug/Frame-eval-01.js b/js/src/jit-test/tests/debug/Frame-eval-01.js new file mode 100644 index 000000000..49a98b60b --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-01.js @@ -0,0 +1,8 @@ +// simplest possible test of Debugger.Frame.prototype.eval + +var g = newGlobal(); +var dbg = new Debugger(g); +var c; +dbg.onDebuggerStatement = function (frame) { c = frame.eval("2 + 2"); }; +g.eval("debugger;"); +assertEq(c.return, 4); diff --git a/js/src/jit-test/tests/debug/Frame-eval-02.js b/js/src/jit-test/tests/debug/Frame-eval-02.js new file mode 100644 index 000000000..541e19bdc --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-02.js @@ -0,0 +1,10 @@ +// frame.eval() throws if frame is not live + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = new Debugger(g); +var f; +dbg.onDebuggerStatement = function (frame) { f = frame; }; +g.eval("debugger;"); +assertThrowsInstanceOf(function () { f.eval("2 + 2"); }, Error); diff --git a/js/src/jit-test/tests/debug/Frame-eval-03.js b/js/src/jit-test/tests/debug/Frame-eval-03.js new file mode 100644 index 000000000..69d6a3243 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-03.js @@ -0,0 +1,19 @@ +// Test eval-ing names in a topmost script frame + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.eval("a").return, 2); + assertEq(frame.eval("c").return, 4); + var exc = frame.eval("d").throw; + assertEq(exc instanceof Debugger.Object, true); + assertEq(exc.proto, frame.eval("ReferenceError.prototype").return); + hits++; +}; +g.eval("function f(a, b) { var c = a + b; debugger; eval('debugger;'); }"); +g.eval("f(2, 2);"); +g.eval("var a = 2, b = 2, c = a + b; debugger;"); +assertEq(hits, 3); diff --git a/js/src/jit-test/tests/debug/Frame-eval-04.js b/js/src/jit-test/tests/debug/Frame-eval-04.js new file mode 100644 index 000000000..93d2433e2 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-04.js @@ -0,0 +1,11 @@ +// frame.eval SyntaxErrors are reflected, not thrown + +var g = newGlobal(); +var dbg = new Debugger(g); +var exc, SEp; +dbg.onDebuggerStatement = function (frame) { + exc = frame.eval("#$@!").throw; + SEp = frame.eval("SyntaxError.prototype").return; +}; +g.eval("debugger;"); +assertEq(exc.proto, SEp); diff --git a/js/src/jit-test/tests/debug/Frame-eval-05.js b/js/src/jit-test/tests/debug/Frame-eval-05.js new file mode 100644 index 000000000..306c5cc73 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-05.js @@ -0,0 +1,14 @@ +// var declarations in strict frame.eval do not modify the frame + +var g = newGlobal(); +var dbg = new Debugger(g); +var cv; +dbg.onDebuggerStatement = function (frame) { + cv = frame.eval("'use strict'; var a = 2; h();"); +}; +g.a = 1; +g.eval("function f(s) { function h() { return a; } eval(s); debugger; } "); +g.eval("f('0');"); +assertEq(cv.return, 1); +g.eval("f('var a = 3;');"); +assertEq(cv.return, 3); diff --git a/js/src/jit-test/tests/debug/Frame-eval-06.js b/js/src/jit-test/tests/debug/Frame-eval-06.js new file mode 100644 index 000000000..a72876cdd --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-06.js @@ -0,0 +1,19 @@ +// frame.eval throws if frame is a generator frame that isn't currently on the stack + +load(libdir + "asserts.js"); + +var g = newGlobal(); +g.eval("function gen(a) { debugger; yield a; }"); +g.eval("function test() { debugger; }"); +var dbg = new Debugger(g); +var genframe; +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + if (frame.callee.name == 'gen') + genframe = frame; + else + assertThrowsInstanceOf(function () { genframe.eval("a"); }, Error); + hits++; +}; +g.eval("var it = gen(42); assertEq(it.next(), 42); test();"); +assertEq(hits, 2); diff --git a/js/src/jit-test/tests/debug/Frame-eval-07.js b/js/src/jit-test/tests/debug/Frame-eval-07.js new file mode 100644 index 000000000..5544a40e8 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-07.js @@ -0,0 +1,31 @@ +// test frame.eval in non-top frames + +var g = newGlobal(); +var N = g.N = 12; // must be even +assertEq(N % 2, 0); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var n = frame.eval("n").return; + if (n === 0) { + for (var i = 0; i <= N; i++) { + assertEq(frame.type, 'call'); + assertEq(frame.callee.name, i % 2 === 0 ? 'even' : 'odd'); + assertEq(frame.eval("n").return, i); + frame = frame.older; + } + assertEq(frame.type, 'call'); + assertEq(frame.callee.name, undefined); + frame = frame.older; + assertEq(frame.type, 'eval'); + hits++; + } +}; + +var result = g.eval("(" + function () { + function odd(n) { return n > 0 && !even(n - 1); } + function even(n) { debugger; return n == 0 || !odd(n - 1); } + return even(N); + } + ")();"); +assertEq(result, true); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Frame-eval-08.js b/js/src/jit-test/tests/debug/Frame-eval-08.js new file mode 100644 index 000000000..bf3421d35 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-08.js @@ -0,0 +1,23 @@ +// The arguments can escape from a function via a debugging hook. + +var g = newGlobal(); +var dbg = new Debugger(g); + +// capture arguments object and test function +var args, testfn; +dbg.onDebuggerStatement = function (frame) { + args = frame.eval("arguments").return; + testfn = frame.eval("test").return; +}; +g.eval("function f() { debugger; }"); +g.eval("var test = " + function test(args) { + assertEq(args.length, 3); + assertEq(args[0], this); + assertEq(args[1], f); + assertEq(args[2].toString(), "[object Object]"); + return 42; + } + ";"); +g.eval("f(this, f, {});"); + +var cv = testfn.apply(null, [args]); +assertEq(cv.return, 42); diff --git a/js/src/jit-test/tests/debug/Frame-eval-09.js b/js/src/jit-test/tests/debug/Frame-eval-09.js new file mode 100644 index 000000000..9efb8b63d --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-09.js @@ -0,0 +1,20 @@ +// assigning to local variables in frame.eval code + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onDebuggerStatement = function (frame) { + frame.eval("outerarg = 1; outervar = 2; innerarg = 3; innervar = 4;"); +}; + +var result = g.eval("(" + function outer(outerarg) { + var outervar = 200; + function inner(innerarg) { + var innervar = 400; + debugger; + return innerarg + innervar; + } + var innersum = inner(300); + return outerarg + outervar + innersum; + } + ")(100)"); + +assertEq(result, 10); diff --git a/js/src/jit-test/tests/debug/Frame-eval-10.js b/js/src/jit-test/tests/debug/Frame-eval-10.js new file mode 100644 index 000000000..ad4a3dac7 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-10.js @@ -0,0 +1,13 @@ +// frame.eval returns null if the eval code fails with an uncatchable error. + +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + if (hits++ === 0) + assertEq(frame.eval("debugger;"), null); + else + return null; +}; +assertEq(g.eval("debugger; 'ok';"), "ok"); +assertEq(hits, 2); diff --git a/js/src/jit-test/tests/debug/Frame-eval-11.js b/js/src/jit-test/tests/debug/Frame-eval-11.js new file mode 100644 index 000000000..ab60cf5d1 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-11.js @@ -0,0 +1,15 @@ +// The arguments can escape from a function via a debugging hook. + +var g = newGlobal(); +var dbg = new Debugger(g); + +// capture arguments object and test function +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.older.eval('arguments[0]').return, 'ponies'); + hits++; +}; +g.eval("function g() { debugger; }"); +g.eval("function f() { g(); }"); +g.eval("f('ponies')"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Frame-eval-12.js b/js/src/jit-test/tests/debug/Frame-eval-12.js new file mode 100644 index 000000000..d0f3ffc2f --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-12.js @@ -0,0 +1,13 @@ +// The arguments can escape from a function via a debugging hook. + +var g = newGlobal(); +var dbg = new Debugger(g); + +// capture arguments object and test function +dbg.onDebuggerStatement = function (frame) { + var args = frame.older.environment.parent.getVariable('arguments'); + assertEq(args.missingArguments, true); +}; +g.eval("function h() { debugger; }"); +g.eval("function f() { var x = 0; return function() { x++; h() } }"); +g.eval("f('ponies')()"); diff --git a/js/src/jit-test/tests/debug/Frame-eval-13.js b/js/src/jit-test/tests/debug/Frame-eval-13.js new file mode 100644 index 000000000..54a90885c --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-13.js @@ -0,0 +1,13 @@ +// The debugger may add new bindings into existing scopes + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onDebuggerStatement = function(frame) { + assertEq(frame.eval("var x = 3; x").return, 3); + hits++; +} +var hits = 0; +g.eval("(function() { debugger; })()"); +assertEq(hits, 1); +g.eval("(function() { var x = 4; debugger; })()"); +assertEq(hits, 2); diff --git a/js/src/jit-test/tests/debug/Frame-eval-14.js b/js/src/jit-test/tests/debug/Frame-eval-14.js new file mode 100644 index 000000000..0b341093f --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-14.js @@ -0,0 +1,26 @@ +// Test the corner case of accessing an unaliased variable of a block +// while the block is not live. + +var g = newGlobal(); +g.eval("function h() { debugger }"); +g.eval("function f() { { let x = 1, y; (function() { y = 0 })(); h() } }"); +g.eval("var surprise = null"); + +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +dbg.onDebuggerStatement = function(hFrame) { + var fFrame = hFrame.older; + assertEq(fFrame.environment.getVariable('x'), 1); + assertEq(fFrame.environment.getVariable('y'), 0); + fFrame.eval("surprise = function() { return ++x }"); + assertEq(gw.executeInGlobal("surprise()").return, 2); +} +g.f(); +assertEq(g.surprise !== null, true); + +// Either succeed or throw an error about 'x' not being live +try { + assertEq(g.surprise(), 3); +} catch (e) { + assertEq(e+'', 'Error: x is not live'); +} diff --git a/js/src/jit-test/tests/debug/Frame-eval-15.js b/js/src/jit-test/tests/debug/Frame-eval-15.js new file mode 100644 index 000000000..0f0843a5d --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-15.js @@ -0,0 +1,13 @@ +var g = newGlobal(); +var dbg = new Debugger(g); + +g.eval("function h() { debugger }"); +g.eval("function f() { h() }"); +g.blah = 42; +dbg.onDebuggerStatement = function(frame) { + frame.older.eval("var blah = 43"); + frame.older.eval("blah = 44"); + assertEq(frame.older.environment.getVariable("blah"), 44); +} +g.f(); +assertEq(g.blah, 42); diff --git a/js/src/jit-test/tests/debug/Frame-eval-16.js b/js/src/jit-test/tests/debug/Frame-eval-16.js new file mode 100644 index 000000000..d7082a12b --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-16.js @@ -0,0 +1,26 @@ +// eval correctly handles optional custom url option +var g = newGlobal(); +var dbg = new Debugger(g); +var count = 0; + +function testUrl (options, expected) { + count++; + dbg.onDebuggerStatement = function (frame) { + dbg.onNewScript = function (script) { + dbg.onNewScript = undefined; + assertEq(script.url, expected); + count--; + }; + frame.eval("", options); + }; + g.eval("debugger;"); +} + + +testUrl(undefined, "debugger eval code"); +testUrl(null, "debugger eval code"); +testUrl({ url: undefined }, "debugger eval code"); +testUrl({ url: null }, "null"); +testUrl({ url: 5 }, "5"); +testUrl({ url: "test" }, "test"); +assertEq(count, 0); diff --git a/js/src/jit-test/tests/debug/Frame-eval-17.js b/js/src/jit-test/tests/debug/Frame-eval-17.js new file mode 100644 index 000000000..eefe63587 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-17.js @@ -0,0 +1,24 @@ +// eval correctly handles optional lineNumber option +var g = newGlobal(); +var dbg = new Debugger(g); +var count = 0; + +function testLineNumber (options, expected) { + count++; + dbg.onDebuggerStatement = function (frame) { + dbg.onNewScript = function (script) { + dbg.onNewScript = undefined; + assertEq(script.startLine, expected); + count--; + }; + frame.eval("", options); + }; + g.eval("debugger;"); +} + + +testLineNumber(undefined, 1); +testLineNumber({}, 1); +testLineNumber({ lineNumber: undefined }, 1); +testLineNumber({ lineNumber: 5 }, 5); +assertEq(count, 0); diff --git a/js/src/jit-test/tests/debug/Frame-eval-18.js b/js/src/jit-test/tests/debug/Frame-eval-18.js new file mode 100644 index 000000000..5dc645fc4 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-18.js @@ -0,0 +1,12 @@ +// yield is not allowed in eval in a star generator. + +load(libdir + 'asserts.js'); + +var g = newGlobal(); +var dbg = new Debugger(g); + +dbg.onDebuggerStatement = function (frame) { + assertThrowsInstanceOf(function() { frame.eval('yield 10;') }, SyntaxError); +}; + +g.eval("(function*g(){ debugger; })()"); diff --git a/js/src/jit-test/tests/debug/Frame-eval-19.js b/js/src/jit-test/tests/debug/Frame-eval-19.js new file mode 100644 index 000000000..3654fb9bb --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-19.js @@ -0,0 +1,34 @@ +// Eval-in-frame of optimized frames to break out of an infinite loop. + +load(libdir + "jitopts.js"); + +if (!jitTogglesMatch(Opts_IonEagerNoOffthreadCompilation)) + quit(0); + +withJitOptions(Opts_IonEagerNoOffthreadCompilation, function () { + var g = newGlobal(); + var dbg = new Debugger; + + g.eval("" + function f(d) { g(d); }); + g.eval("" + function g(d) { h(d); }); + g.eval("" + function h(d) { + var i = 0; + while (d) + interruptIf(d && i++ == 4000); + }); + + setInterruptCallback(function () { + dbg.addDebuggee(g); + var frame = dbg.getNewestFrame(); + if (frame.callee.name != "h" || frame.implementation != "ion") + return true; + frame.eval("d = false;"); + return true; + }); + + g.eval("(" + function () { + for (i = 0; i < 5; i++) + f(false); + f(true); + } + ")();"); +}); diff --git a/js/src/jit-test/tests/debug/Frame-eval-20.js b/js/src/jit-test/tests/debug/Frame-eval-20.js new file mode 100644 index 000000000..4622e5a8a --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-20.js @@ -0,0 +1,46 @@ +// Eval-in-frame with different type on non-youngest Ion frame. + +load(libdir + "jitopts.js"); + +if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation)) + quit(0); + +withJitOptions(Opts_Ion2NoOffthreadCompilation, function () { + function test(shadow) { + var g = newGlobal(); + var dbg = new Debugger; + + // Note that we depend on CCW scripted functions being opaque to Ion + // optimization for this test. + g.h = function h(d) { + if (d) { + dbg.addDebuggee(g); + var f = dbg.getNewestFrame().older; + assertEq(f.implementation, "ion"); + assertEq(f.environment.getVariable("foo"), 42); + + // EIF of a different type too. + f.eval((shadow ? "var " : "") + "foo = 'string of 42'"); + g.expected = shadow ? 42 : "string of 42"; + } + } + + g.eval("" + function f(d) { + var foo = 42; + g(d); + return foo; + }); + g.eval("" + function g(d) { + h(d); + }); + + g.eval("(" + function () { + for (i = 0; i < 5; i++) + f(false); + assertEq(f(true), "string of 42"); + } + ")();"); + } + + test(false); + test(true); +}); diff --git a/js/src/jit-test/tests/debug/Frame-eval-21.js b/js/src/jit-test/tests/debug/Frame-eval-21.js new file mode 100644 index 000000000..097eb5078 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-21.js @@ -0,0 +1,33 @@ +// Eval-in-frame with different type on baseline frame with let-scoping + +load(libdir + "jitopts.js"); + +if (!jitTogglesMatch(Opts_BaselineEager)) + quit(0); + +withJitOptions(Opts_BaselineEager, function () { + var g = newGlobal(); + var dbg = new Debugger; + + g.h = function h(d) { + if (d) { + dbg.addDebuggee(g); + var f = dbg.getNewestFrame().older; + assertEq(f.implementation, "baseline"); + assertEq(f.environment.getVariable("foo"), 42); + f.eval("foo = 'string of 42'"); + } + } + + g.eval("" + function f(d) { + if (d) { + let foo = 42; + g(d); + return foo; + } + }); + + g.eval("" + function g(d) { h(d); }); + + g.eval("(" + function () { assertEq(f(true), "string of 42"); } + ")();"); +}); diff --git a/js/src/jit-test/tests/debug/Frame-eval-22.js b/js/src/jit-test/tests/debug/Frame-eval-22.js new file mode 100644 index 000000000..f081ec495 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-22.js @@ -0,0 +1,32 @@ +// Debugger.Frame preserves Ion frame identity + +load(libdir + "jitopts.js"); + +if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation)) + quit(); + +withJitOptions(Opts_Ion2NoOffthreadCompilation, function () { + var g = newGlobal(); + var dbg1 = new Debugger; + var dbg2 = new Debugger; + + g.toggle = function toggle(x, d) { + if (d) { + dbg1.addDebuggee(g); + dbg2.addDebuggee(g); + var frame1 = dbg1.getNewestFrame(); + assertEq(frame1.environment.getVariable("x"), x); + assertEq(frame1.implementation, "ion"); + frame1.environment.setVariable("x", "not 42"); + assertEq(dbg2.getNewestFrame().environment.getVariable("x"), "not 42"); + } + }; + + g.eval("" + function f(x, d) { toggle(x, d); }); + + g.eval("(" + function test() { + for (var i = 0; i < 5; i++) + f(42, false); + f(42, true); + } + ")();"); +}); diff --git a/js/src/jit-test/tests/debug/Frame-eval-23.js b/js/src/jit-test/tests/debug/Frame-eval-23.js new file mode 100644 index 000000000..45070d82d --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-23.js @@ -0,0 +1,37 @@ +// Debugger.Frame preserves Ion frame mutations after removing debuggee. + +load(libdir + "jitopts.js"); + +if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation)) + quit(); + +withJitOptions(Opts_Ion2NoOffthreadCompilation, function () { + var g = newGlobal(); + var dbg = new Debugger; + + g.toggle = function toggle(x, d) { + if (d) { + dbg.addDebuggee(g); + var frame = dbg.getNewestFrame().older; + assertEq(frame.callee.name, "f"); + assertEq(frame.environment.getVariable("x"), x); + assertEq(frame.implementation, "ion"); + frame.environment.setVariable("x", "not 42"); + dbg.removeDebuggee(g); + } + }; + + g.eval("" + function f(x, d) { + g(x, d); + if (d) + assertEq(x, "not 42"); + }); + + g.eval("" + function g(x, d) { toggle(x, d); }); + + g.eval("(" + function test() { + for (var i = 0; i < 5; i++) + f(42, false); + f(42, true); + } + ")();"); +}); diff --git a/js/src/jit-test/tests/debug/Frame-eval-24.js b/js/src/jit-test/tests/debug/Frame-eval-24.js new file mode 100644 index 000000000..b731771c6 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-24.js @@ -0,0 +1,24 @@ +// Make sure the getVariable/setVariable/eval functions work correctly with +// unaliased locals. +var g = newGlobal(); +g.eval('\ +function g() { debugger; };\ +function f(arg) {\ + var y = arg - 3;\ + var a1 = 1;\ + var a2 = 1;\ + var b = arg + 9;\ + var z = function() { return a1 + a2; };\ + g();\ +};'); + +var dbg = new Debugger(g); + +dbg.onDebuggerStatement = function handleDebugger(frame) { + assertEq(frame.older.eval("y + b").return, 26); + assertEq(frame.older.environment.getVariable("y"), 7); + frame.older.environment.setVariable("b", 4); + assertEq(frame.older.eval("y + b").return, 11); +}; + +g.f(10); diff --git a/js/src/jit-test/tests/debug/Frame-eval-25.js b/js/src/jit-test/tests/debug/Frame-eval-25.js new file mode 100644 index 000000000..cc91b2858 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-25.js @@ -0,0 +1,25 @@ +// Make sure we can recover missing arguments even when it gets assigned to +// another slot. + +load(libdir + "asserts.js"); +load(libdir + "evalInFrame.js"); + +function h() { + evalInFrame(1, "a.push(0)"); +} + +function f() { + var a = arguments; + h(); +} + +assertThrowsInstanceOf(f, TypeError); + +function g() { + { + let a = arguments; + h(); + } +} + +assertThrowsInstanceOf(g, TypeError); diff --git a/js/src/jit-test/tests/debug/Frame-eval-26.js b/js/src/jit-test/tests/debug/Frame-eval-26.js new file mode 100644 index 000000000..ea11f0f4e --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-26.js @@ -0,0 +1,16 @@ +// Bug 1026477: Defining functions with D.F.p.eval works, even if there's +// already a non-aliased var binding for the identifier. + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onDebuggerStatement = function (frame) { + frame.older.eval('function f() { }'); +}; + +// When the compiler sees the 'debugger' statement, it marks all variables as +// aliased, but we want to test the case where f is in a stack frame slot, so we +// put the 'debugger' statement in a separate function, and use frame.older to +// get back to the anonymous function's frame. +g.eval('function q() { debugger; }'); +assertEq(typeof g.eval('(function () { var f = 42; q(); return f; })();'), + "function"); diff --git a/js/src/jit-test/tests/debug/Frame-eval-27.js b/js/src/jit-test/tests/debug/Frame-eval-27.js new file mode 100644 index 000000000..4c7d5fb02 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-27.js @@ -0,0 +1,13 @@ +// Bug 1026477: Defining functions with D.F.p.eval works, even if there's +// already a var binding for the identifier. + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onDebuggerStatement = function (frame) { + frame.eval('function f() { }'); +}; + +// When the compiler sees the 'debugger' statement, it marks all variables as +// aliased, so f will live in a Call object. +assertEq(typeof g.eval('(function () { var f = 42; debugger; return f;})();'), + "function"); diff --git a/js/src/jit-test/tests/debug/Frame-eval-28.js b/js/src/jit-test/tests/debug/Frame-eval-28.js new file mode 100644 index 000000000..e70281697 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-28.js @@ -0,0 +1,12 @@ +// Test that strict Debugger.Frame.eval has a correct static scope. +options('strict_mode'); +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onEnterFrame = function(f) { + hits++; + if (hits > 2) + return; + f.eval("42"); +}; +g.eval("42"); diff --git a/js/src/jit-test/tests/debug/Frame-eval-29.js b/js/src/jit-test/tests/debug/Frame-eval-29.js new file mode 100644 index 000000000..73a3e735e --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-29.js @@ -0,0 +1,59 @@ +// Test reading and setting values on "hollow" debug scopes. In testGet and +// testSet below, f and g *must* be called from a non-heavyweight lambda to +// trigger the creation of the "hollow" debug scopes for the missing scopes. +// +// The reason is that a direct call to f or g that accesses a in testGet or +// testSet's frame is actually recoverable. The Debugger can synthesize a scope +// based on the frame. By contorting through a lambda, it becomes unsound to +// synthesize a scope based on the lambda function's frame. Since f and g are +// accessing a, which is itself free inside the lambda, the Debugger has no way +// to tell if the on-stack testGet or testSet frame is the frame that *would +// have* allocated a scope for the lambda, *had the lambda been heavyweight*. +// +// More concretely, if the inner lambda were returned from testGet and testSet, +// then called from a different invocation of testGet or testSet, it becomes +// obvious that it is incorrect to synthesize a scope based on the frame of +// that different invocation. + +load(libdir + "evalInFrame.js"); + +function f() { + // Eval one frame up. Nothing aliases a. + evalInFrame(1, "print(a)"); +} + +function g() { + evalInFrame(1, "a = 43"); +} + +function testGet() { + { + let a = 42; + (function () { f(); })(); + } +} + +function testSet() { + { + let a = 42; + (function () { g(); })(); + } +} + +var log = ""; + +try { + testGet(); +} catch (e) { + // Throws due to a having been optimized out. + log += "g"; +} + +try { + testSet(); +} catch (e) { + // Throws due to a having been optimized out. + log += "s"; +} + +assertEq(log, "gs"); diff --git a/js/src/jit-test/tests/debug/Frame-eval-30.js b/js/src/jit-test/tests/debug/Frame-eval-30.js new file mode 100644 index 000000000..f99912c45 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-30.js @@ -0,0 +1,19 @@ +// Test that Debugger.Frame.eval correctly throws on redeclaration. + +load(libdir + "evalInFrame.js"); + +let x; + +function f() { + evalInFrame(1, "var x;"); +} + +var log = ""; + +try { + f(); +} catch (e) { + log += "e"; +} + +assertEq(log, "e"); diff --git a/js/src/jit-test/tests/debug/Frame-eval-31.js b/js/src/jit-test/tests/debug/Frame-eval-31.js new file mode 100644 index 000000000..bef94a046 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-31.js @@ -0,0 +1,9 @@ +// evalInFrame with non-syntactic scopes. + +load(libdir + "asserts.js"); +load(libdir + "evalInFrame.js"); + +evalReturningScope(` + var x = 42; + assertEq(evalInFrame(0, "x"), 42); +`); diff --git a/js/src/jit-test/tests/debug/Frame-eval-32.js b/js/src/jit-test/tests/debug/Frame-eval-32.js new file mode 100644 index 000000000..9a5309d6a --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-32.js @@ -0,0 +1,8 @@ +// |jit-test| error: ReferenceError + +// Test the TDZ works for glbao lexicals through Debugger environments in +// compound assignments. +load(libdir + "evalInFrame.js"); + +evalInFrame(0, "x |= 0"); +let x; diff --git a/js/src/jit-test/tests/debug/Frame-eval-stack.js b/js/src/jit-test/tests/debug/Frame-eval-stack.js new file mode 100644 index 000000000..1199dfd72 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-eval-stack.js @@ -0,0 +1,19 @@ +var g = newGlobal(); +var dbg = new Debugger(g); + +g.eval("function h() { debugger; }"); +g.eval("function g() { h() }"); +g.eval("function f() { var blah = 333; g() }"); + +dbg.onDebuggerStatement = function(frame) { + frame = frame.older; + g.trace = frame.older.eval("(new Error()).stack;").return; +} +g.f(); + +assertEq(typeof g.trace, "string"); + +var frames = g.trace.split("\n"); +assertEq(frames[0].includes("eval code"), true); +assertEq(frames[1].startsWith("f@"), true); +assertEq(frames[2].startsWith("@"), true); diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-01.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-01.js new file mode 100644 index 000000000..e802d9d27 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-01.js @@ -0,0 +1,35 @@ +// evalWithBindings basics + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.evalWithBindings("x", {x: 2}).return, 2); + assertEq(frame.evalWithBindings("x + y", {x: 2}).return, 5); + hits++; +}; + +// in global code +g.y = 3; +g.eval("debugger;"); + +// in function code +g.y = "fail"; +g.eval("function f(y) { debugger; }"); +g.f(3); + +// in direct eval code +g.eval("function f() { var y = 3; eval('debugger;'); }"); +g.f(); + +// in strict eval code with var +g.eval("function f() { 'use strict'; eval('var y = 3; debugger;'); }"); +g.f(); + +// in a with block +g.eval("with ({y: 3}) { debugger; }"); + +// shadowing +g.eval("{ let x = 50, y = 3; debugger; }"); + +assertEq(hits, 6); diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-02.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-02.js new file mode 100644 index 000000000..248aa8a20 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-02.js @@ -0,0 +1,21 @@ +// evalWithBindings to call a method of a debuggee value + +var g = newGlobal(); +var dbg = new Debugger; +var global = dbg.addDebuggee(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var obj = frame.arguments[0]; + var expected = frame.arguments[1]; + assertEq(frame.evalWithBindings("obj.toString()", {obj: obj}).return, expected); + hits++; +}; + +g.eval("function f(obj, expected) { debugger; }"); + +g.eval("f(new Number(-0), '0');"); +g.eval("f(new String('ok'), 'ok');"); +g.eval("f(Symbol('still ok'), 'Symbol(still ok)');"); +g.eval("f(Object(Symbol('still ok')), 'Symbol(still ok)');"); +g.eval("f({toString: function () { return f; }}, f);"); +assertEq(hits, 5); diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-03.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-03.js new file mode 100644 index 000000000..f3a3e4185 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-03.js @@ -0,0 +1,16 @@ +// arguments works in evalWithBindings (it does not interpose a function scope) +var g = newGlobal(); +var dbg = new Debugger; +var global = dbg.addDebuggee(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var argc = frame.arguments.length; + assertEq(argc, 8); + assertEq(frame.evalWithBindings("arguments[prop]", {prop: "length"}).return, argc); + for (var i = 0; i < argc; i++) + assertEq(frame.evalWithBindings("arguments[i]", {i: i}).return, frame.arguments[i]); + hits++; +}; +g.eval("function f() { debugger; }"); +g.eval("f(undefined, -0, NaN, '\uffff', Symbol('alpha'), Array.prototype, Math, f);"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-04.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-04.js new file mode 100644 index 000000000..d828a1183 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-04.js @@ -0,0 +1,17 @@ +// evalWithBindings works on non-top frames. +var g = newGlobal(); +var dbg = new Debugger(g); +var f1; +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.older.evalWithBindings("q + r", {r: 3}).return, 5); + + // frame.older.older is in the same function as frame, but a different activation of it + assertEq(frame.older.older.evalWithBindings("q + r", {r: 3}).return, 6); + hits++; +}; + +g.eval("function f1(q) { if (q == 1) debugger; else f2(2); }"); +g.eval("function f2(arg) { var q = arg; f1(1); }"); +g.f1(3); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-05.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-05.js new file mode 100644 index 000000000..aa8dbd76d --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-05.js @@ -0,0 +1,12 @@ +// evalWithBindings code can assign to the bindings. +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.evalWithBindings("for (i = 0; i < 5; i++) {} i;", {i: 10}).return, 5); + hits++; +}; + +g.eval("debugger;"); +assertEq("i" in g, false); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-06.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-06.js new file mode 100644 index 000000000..f1ca7c8f0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-06.js @@ -0,0 +1,9 @@ +// In evalWithBindings code, assignment to any name not in the bindings works just as in eval. +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.evalWithBindings("y = z; x = w;", {z: 2, w: 3}).return, 3); +}; +g.eval("function f(x) { debugger; assertEq(x, 3); }"); +g.eval("var y = 0; f(0);"); +assertEq(g.y, 2); diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-07.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-07.js new file mode 100644 index 000000000..9113198db --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-07.js @@ -0,0 +1,16 @@ +// var statements in strict evalWithBindings code behave like strict eval. +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.evalWithBindings("var i = a*a + b*b; i === 25;", {a: 3, b: 4}).return, true); + hits++; +}; +g.eval("'use strict'; debugger;"); +assertEq(hits, 1); +assertEq("i" in g, false); + +g.eval("function die() { throw fit; }"); +g.eval("Object.defineProperty(this, 'i', {get: die, set: die});"); +g.eval("'use strict'; debugger;"); +assertEq(hits, 2); diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-08.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-08.js new file mode 100644 index 000000000..48e51ba6a --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-08.js @@ -0,0 +1,13 @@ +// evalWithBindings ignores non-enumerable and non-own properties. +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.evalWithBindings("toString + constructor + length", []).return, 112233); + var obj = Object.create({constructor: "FAIL"}, {length: {value: "fail"}}); + assertEq(frame.evalWithBindings("toString + constructor + length", obj).return, 112233); + hits++; +}; +g.eval("function f() { var toString = 111111, constructor = 1111, length = 11; debugger; }"); +g.f(); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-09.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-09.js new file mode 100644 index 000000000..8b9419913 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-09.js @@ -0,0 +1,27 @@ +// evalWithBindings code is debuggee code, so it can trip the debugger. It nests! +var g = newGlobal(); +var dbg = new Debugger(g); +var f1; +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + f1 = frame; + + // This trips the onExceptionUnwind hook. + var x = frame.evalWithBindings("wrongSpeling", {rightSpelling: 2}).throw; + + assertEq(frame.evalWithBindings("exc.name", {exc: x}).return, "ReferenceError"); + hits++; +}; +dbg.onExceptionUnwind = function (frame, exc) { + assertEq(frame !== f1, true); + + // f1's environment does not contain the binding for the first evalWithBindings call. + assertEq(f1.eval("rightSpelling").return, "dependent"); + assertEq(f1.evalWithBindings("n + rightSpelling", {n: "in"}).return, "independent"); + + // frame's environment does contain the binding. + assertEq(frame.eval("rightSpelling").return, 2); + assertEq(frame.evalWithBindings("rightSpelling + three", {three: 3}).return, 5); + hits++; +}; +g.eval("(function () { var rightSpelling = 'dependent'; debugger; })();"); diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-10.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-10.js new file mode 100644 index 000000000..61fe4eee6 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-10.js @@ -0,0 +1,16 @@ +// Direct eval code under evalWithBindings sees both the bindings and the enclosing scope. + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var code = + "assertEq(a, 1234);\n" + + "assertEq(b, null);\n" + + "assertEq(c, 'ok');\n"; + assertEq(frame.evalWithBindings("eval(s)", {s: code, a: 1234}).return, undefined); + hits++; +}; +g.eval("function f(b) { var c = 'ok'; debugger; }"); +g.f(null); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-11.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-11.js new file mode 100644 index 000000000..b1df83a19 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-11.js @@ -0,0 +1,18 @@ +// var statements in non-strict evalWithBindings code behave like non-strict direct eval. +var g = newGlobal(); +var dbg = new Debugger(g); +var log; +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.evalWithBindings("var i = v; 42;", { v: 'inner' }).return, 42); +}; + +g.i = 'outer'; +log = ''; +assertEq(g.eval('debugger; i;'), 'inner'); +assertEq(log, 'd'); + +g.j = 'outer'; +log = ''; +assertEq(g.eval('debugger; j;'), 'outer'); +assertEq(log, 'd'); diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-12.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-12.js new file mode 100644 index 000000000..2a0bca435 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-12.js @@ -0,0 +1,26 @@ +// evalWithBindings correctly handles optional custom url option +var g = newGlobal(); +var dbg = new Debugger(g); +var count = 0; + +function testUrl (options, expected) { + count++; + dbg.onDebuggerStatement = function (frame) { + dbg.onNewScript = function (script) { + dbg.onNewScript = undefined; + assertEq(script.url, expected); + count--; + }; + frame.evalWithBindings("", {}, options); + }; + g.eval("debugger;"); +} + + +testUrl(undefined, "debugger eval code"); +testUrl(null, "debugger eval code"); +testUrl({ url: undefined }, "debugger eval code"); +testUrl({ url: null }, "null"); +testUrl({ url: 5 }, "5"); +testUrl({ url: "test" }, "test"); +assertEq(count, 0); diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-13.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-13.js new file mode 100644 index 000000000..2c26b6ea4 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-13.js @@ -0,0 +1,24 @@ +// evalWithBindings correctly handles optional lineNumber option +var g = newGlobal(); +var dbg = new Debugger(g); +var count = 0; + +function testLineNumber (options, expected) { + count++; + dbg.onDebuggerStatement = function (frame) { + dbg.onNewScript = function (script) { + dbg.onNewScript = undefined; + assertEq(script.startLine, expected); + count--; + }; + frame.evalWithBindings("", {}, options); + }; + g.eval("debugger;"); +} + + +testLineNumber(undefined, 1); +testLineNumber({}, 1); +testLineNumber({ lineNumber: undefined }, 1); +testLineNumber({ lineNumber: 5 }, 5); +assertEq(count, 0); diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-14.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-14.js new file mode 100644 index 000000000..1c8fc93cb --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-14.js @@ -0,0 +1,20 @@ +var g = newGlobal(); +var dbg = new Debugger(g); +// We're going to need to eval with a thrown exception from inside +// onExceptionUnwind, so guard against infinite recursion. +var exceptionCount = 0; +dbg.onDebuggerStatement = function (frame) { + var x = frame.evalWithBindings("throw 'haha'", { rightSpelling: 4 }).throw; + assertEq(x, "haha"); +}; +dbg.onExceptionUnwind = function (frame, exc) { + ++exceptionCount; + if (exceptionCount == 1) { + var y = frame.evalWithBindings("throw 'haha2'", { rightSpelling: 2 }).throw; + assertEq(y, "haha2"); + } else { + assertEq(frame.evalWithBindings("rightSpelling + three", { three : 3 }).return, 5); + } +}; +g.eval("(function () { var rightSpelling = 7; debugger; })();"); +assertEq(exceptionCount, 2); diff --git a/js/src/jit-test/tests/debug/Frame-evalWithBindings-15.js b/js/src/jit-test/tests/debug/Frame-evalWithBindings-15.js new file mode 100644 index 000000000..319eaaeb3 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-evalWithBindings-15.js @@ -0,0 +1,15 @@ +var g = newGlobal(); +var dbg = new Debugger(g); + +dbg.onDebuggerStatement = function (frame) { + // The bindings object is unused but adds another environment on the + // environment chain. Make sure 'this' computes the right value in light of + // this. + assertEq(frame.evalWithBindings(`this === foo;`, { bar: 42 }).return, true); + assertEq(frame.evalWithBindings(`eval('this') === foo;`, { bar: 42 }).return, true); +}; + +g.eval(` +var foo = { bar: function() { debugger; } }; +foo.bar(); +`); diff --git a/js/src/jit-test/tests/debug/Frame-identity-01.js b/js/src/jit-test/tests/debug/Frame-identity-01.js new file mode 100644 index 000000000..b06a33399 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-identity-01.js @@ -0,0 +1,19 @@ +// Check that {return:} resumption kills the current stack frame. + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.eval("(" + function () { + var dbg = new Debugger(debuggeeGlobal); + var prev = null; + dbg.onDebuggerStatement = function (frame) { + assertEq(frame === prev, false); + if (prev) + assertEq(prev.live, false); + prev = frame; + return {return: frame.arguments[0]}; + }; + } + ")();"); + +function f(i) { debugger; } +for (var i = 0; i < 10; i++) + assertEq(f(i), i); diff --git a/js/src/jit-test/tests/debug/Frame-identity-02.js b/js/src/jit-test/tests/debug/Frame-identity-02.js new file mode 100644 index 000000000..207e6879e --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-identity-02.js @@ -0,0 +1,21 @@ +// Check that {throw:} resumption kills the current stack frame. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.eval("(" + function () { + var dbg = new Debugger(debuggeeGlobal); + var prev = null; + dbg.onDebuggerStatement = function (frame) { + assertEq(frame === prev, false); + if (prev) + assertEq(prev.live, false); + prev = frame; + return {throw: debuggeeGlobal.i}; + }; + } + ")();"); + +function f() { debugger; } +for (var i = 0; i < 10; i++) + assertThrowsValue(f, i); diff --git a/js/src/jit-test/tests/debug/Frame-identity-03.js b/js/src/jit-test/tests/debug/Frame-identity-03.js new file mode 100644 index 000000000..19c62def8 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-identity-03.js @@ -0,0 +1,49 @@ +// Test that we create new Debugger.Frames and reuse old ones correctly with recursion. + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.eval("(" + function () { + function id(f) { + return ("id" in f) ? f.id : (f.id = nextid++); + } + + var dbg = new Debugger(debuggeeGlobal); + dbg.onDebuggerStatement = function (frame) { + var a = []; + for (; frame; frame = frame.older) + a.push(frame); + var s = ''; + while (a.length) + s += id(a.pop()); + results.push(s); + }; + } + ")();"); + +function cons(a, b) { + debugger; + return [a, b]; +} + +function tree(n) { + if (n < 2) + return n; + return cons(tree(n - 1), tree(n - 2)); +} + +g.eval("results = []; nextid = 0;"); +debugger; +assertEq(g.results.join(","), "0"); +assertEq(g.nextid, 1); + +g.eval("results = [];"); +tree(2); +assertEq(g.results.join(","), "012"); // 0=global, 1=tree, 2=cons + +g.eval("results = []; nextid = 1;"); +tree(3); +assertEq(g.results.join(","), "0123,014"); //0=global, 1=tree(3), 2=tree(2), 3=cons, 4=cons + +g.eval("results = []; nextid = 1;"); +tree(4); +// 0=global, 1=tree(4), 2=tree(3), 3=tree(2), 4=cons, tree(1), 5=cons, 6=tree(2), 7=cons, 8=cons +assertEq(g.results.join(","), "01234,0125,0167,018"); diff --git a/js/src/jit-test/tests/debug/Frame-identity-04.js b/js/src/jit-test/tests/debug/Frame-identity-04.js new file mode 100644 index 000000000..7aea809b0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-identity-04.js @@ -0,0 +1,20 @@ +// Test that on-stack Debugger.Frames are not GC'd even if they are only reachable +// from the js::Debugger::frames table. + +var g = newGlobal(); +g.eval("function f(n) { if (n) f(n - 1); debugger; }"); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + if (hits === 0) { + for (; frame; frame = frame.older) + frame.seen = true; + } else { + for (; frame; frame = frame.older) + assertEq(frame.seen, true); + } + gc(); + hits++; +}; +g.f(20); +assertEq(hits, 21); diff --git a/js/src/jit-test/tests/debug/Frame-implementation-01.js b/js/src/jit-test/tests/debug/Frame-implementation-01.js new file mode 100644 index 000000000..e26c4ddd1 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-implementation-01.js @@ -0,0 +1,45 @@ +// Debugger.Frames of all implementations. + +load(libdir + "jitopts.js"); + +function testFrameImpl(jitopts, assertFrameImpl) { + if (!jitTogglesMatch(jitopts)) + return; + + withJitOptions(jitopts, function () { + var g = newGlobal(); + var dbg = new Debugger; + + g.toggle = function toggle(d) { + if (d) { + dbg.addDebuggee(g); + var frame = dbg.getNewestFrame(); + // We only care about the f and g frames. + for (var i = 0; i < 2; i++) { + assertFrameImpl(frame); + frame = frame.older; + } + } + }; + + g.eval("" + function f(d) { g(d); }); + g.eval("" + function g(d) { toggle(d); }); + + g.eval("(" + function test() { + for (var i = 0; i < 5; i++) + f(false); + f(true); + } + ")();"); + }); +} + +[[Opts_BaselineEager, + function (f) { assertEq(f.implementation, "baseline"); }], + // Note that the Ion case *depends* on CCW scripted functions being opaque to + // Ion optimization and not deoptimizing the frames below the call to toggle. + [Opts_Ion2NoOffthreadCompilation, + function (f) { assertEq(f.implementation, "ion"); }], + [Opts_NoJits, + function (f) { assertEq(f.implementation, "interpreter"); }]].forEach(function ([opts, fn]) { + testFrameImpl(opts, fn); + }); diff --git a/js/src/jit-test/tests/debug/Frame-implementation-02.js b/js/src/jit-test/tests/debug/Frame-implementation-02.js new file mode 100644 index 000000000..41b4d6794 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-implementation-02.js @@ -0,0 +1,51 @@ +// Test that Ion frames are invalidated by turning on Debugger. Invalidation +// is unobservable, but we know that Ion scripts cannot handle Debugger +// handlers, so we test for the handlers being called. + +load(libdir + "jitopts.js"); + +if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation)) + quit(); + +withJitOptions(Opts_Ion2NoOffthreadCompilation, function () { + var g = newGlobal(); + var dbg = new Debugger; + var onPopExecuted = false; + var breakpointHit = false; + + g.toggle = function toggle(d) { + if (d) { + dbg.addDebuggee(g); + + var frame1 = dbg.getNewestFrame(); + assertEq(frame1.implementation, "ion"); + frame1.onPop = function () { + onPopExecuted = true; + }; + + var frame2 = frame1.older; + assertEq(frame2.implementation, "ion"); + // Offset of |print(42 + 42)| + var offset = frame2.script.getLineOffsets(3)[0]; + frame2.script.setBreakpoint(offset, { hit: function (fr) { + assertEq(fr.implementation != "ion", true); + breakpointHit = true; + }}); + } + }; + + g.eval("" + function f(d) { + g(d); + print(42 + 42); + }); + g.eval("" + function g(d) { toggle(d); }); + + g.eval("(" + function test() { + for (var i = 0; i < 5; i++) + f(false); + f(true); + } + ")();"); + + assertEq(onPopExecuted, true); + assertEq(breakpointHit, true); +}); diff --git a/js/src/jit-test/tests/debug/Frame-live-01.js b/js/src/jit-test/tests/debug/Frame-live-01.js new file mode 100644 index 000000000..192602e1b --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-live-01.js @@ -0,0 +1,41 @@ +// Debugger.Frame.prototype.live is true for frames on the stack and false for +// frames that have returned + +var desc = Object.getOwnPropertyDescriptor(Debugger.Frame.prototype, "live"); +assertEq(typeof desc.get, "function"); +assertEq(desc.set, undefined); +assertEq(desc.configurable, true); +assertEq(desc.enumerable, false); + +var loc; + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.eval("var hits = 0;"); +g.eval("(" + function () { + var a = []; + var dbg = Debugger(debuggeeGlobal); + dbg.onDebuggerStatement = function (frame) { + var loc = debuggeeGlobal.loc; + a[loc] = frame; + for (var i = 0; i < a.length; i++) { + assertEq(a[i] === frame, i === loc); + assertEq(!!(a[i] && a[i].live), i >= loc); + } + hits++; + }; + } + ")()"); + +function f(n) { + loc = n; debugger; + if (n !== 0) { + f(n - 1); + loc = n; debugger; + eval("f(n - 1);"); + loc = n; debugger; + } +} + +f(4); +assertEq(g.hits, 16 + 8*3 + 4*3 + 2*3 + 1*3); + diff --git a/js/src/jit-test/tests/debug/Frame-live-02.js b/js/src/jit-test/tests/debug/Frame-live-02.js new file mode 100644 index 000000000..f0961ad00 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-live-02.js @@ -0,0 +1,32 @@ +// Debugger.Frame.prototype.live is false for frames that have thrown or been thrown through + +load(libdir + "asserts.js"); + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.eval("var finalCheck;"); +g.eval("(" + function () { + var a = []; + var dbg = Debugger(debuggeeGlobal); + dbg.onDebuggerStatement = function (frame) { + a.push(frame); + for (var i = 0; i < a.length; i++) + assertEq(a[i].live, true); + }; + finalCheck = function (n) { + assertEq(a.length, n); + for (var i = 0; i < n; i++) + assertEq(a[i].live, false); + }; + } + ")()"); + +function f(n) { + debugger; + if (--n > 0) + f(n); + else + throw "fit"; +} + +assertThrowsValue(function () { f(10); }, "fit"); +g.finalCheck(10); diff --git a/js/src/jit-test/tests/debug/Frame-live-03.js b/js/src/jit-test/tests/debug/Frame-live-03.js new file mode 100644 index 000000000..aa8283cbf --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-live-03.js @@ -0,0 +1,27 @@ +// frame properties throw if !frame.live + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var f; +Debugger(g).onDebuggerStatement = function (frame) { + assertEq(frame.live, true); + assertEq(frame.type, "call"); + assertEq(frame.this instanceof Object, true); + assertEq(frame.older instanceof Debugger.Frame, true); + assertEq(frame.callee instanceof Debugger.Object, true); + assertEq(frame.generator, false); + assertEq(frame.constructing, false); + assertEq(frame.arguments.length, 0); + f = frame; +}; + +g.eval("(function () { debugger; }).call({});"); +assertEq(f.live, false); +assertThrowsInstanceOf(function () { f.type; }, Error); +assertThrowsInstanceOf(function () { f.this; }, Error); +assertThrowsInstanceOf(function () { f.older; }, Error); +assertThrowsInstanceOf(function () { f.callee; }, Error); +assertThrowsInstanceOf(function () { f.generator; }, Error); +assertThrowsInstanceOf(function () { f.constructing; }, Error); +assertThrowsInstanceOf(function () { f.arguments; }, Error); diff --git a/js/src/jit-test/tests/debug/Frame-live-04.js b/js/src/jit-test/tests/debug/Frame-live-04.js new file mode 100644 index 000000000..c4c9a4d06 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-live-04.js @@ -0,0 +1,27 @@ +// frame.live is false for frames discarded during uncatchable error unwinding. + +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +var snapshot; +dbg.onDebuggerStatement = function (frame) { + var stack = []; + for (var f = frame; f; f = f.older) { + if (f.type === "call" && f.script !== null) + stack.push(f); + } + snapshot = stack; + if (hits++ === 0) + assertEq(frame.eval("x();"), null); + else + return null; +}; + +g.eval("function z() { debugger; }"); +g.eval("function y() { z(); }"); +g.eval("function x() { y(); }"); +assertEq(g.eval("debugger; 'ok';"), "ok"); +assertEq(hits, 2); +assertEq(snapshot.length, 3); +for (var i = 0; i < snapshot.length; i++) + assertEq(snapshot[i].live, false); diff --git a/js/src/jit-test/tests/debug/Frame-live-05.js b/js/src/jit-test/tests/debug/Frame-live-05.js new file mode 100644 index 000000000..b8bcc6f9e --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-live-05.js @@ -0,0 +1,29 @@ +// frame.live is false for frames removed after their compartments stopped being debuggees. + +var g1 = newGlobal(); +var g2 = newGlobal(); +var dbg = Debugger(g1, g2); +var hits = 0; +var snapshot = []; +dbg.onDebuggerStatement = function (frame) { + if (hits++ === 0) { + assertEq(frame.eval("x();"), null); + } else { + for (var f = frame; f; f = f.older) { + if (f.type === "call" && f.script !== null) + snapshot.push(f); + } + dbg.removeDebuggee(g2); + return null; + } +}; + +g1.eval("function z() { debugger; }"); +g2.z = g1.z; +g2.eval("function y() { z(); }"); +g2.eval("function x() { y(); }"); +assertEq(g2.eval("debugger; 'ok';"), "ok"); +assertEq(hits, 2); +assertEq(snapshot.length, 3); +for (var i = 0; i < snapshot.length; i++) + assertEq(snapshot[i].live, false); diff --git a/js/src/jit-test/tests/debug/Frame-newTargetEval-01.js b/js/src/jit-test/tests/debug/Frame-newTargetEval-01.js new file mode 100644 index 000000000..4a40d622c --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-newTargetEval-01.js @@ -0,0 +1,40 @@ +// Test that new.target is acceptably usable in RematerializedFrames. + +gczeal(0); + +load(libdir + "jitopts.js"); + +if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation)) + quit(); + +withJitOptions(Opts_Ion2NoOffthreadCompilation, function () { + var g = newGlobal(); + var dbg = new Debugger; + + g.toggle = function toggle(d, expected) { + if (d) { + dbg.addDebuggee(g); + + var frame = dbg.getNewestFrame(); + assertEq(frame.implementation, "ion"); + assertEq(frame.constructing, true); + + // CONGRATS IF THIS FAILS! You, proud saviour, have made new.target parse + // in debug frame evals (presumably by hooking up static scope walks). + // Uncomment the assert below for efaust's undying gratitude. + // Note that we use .name here because of CCW nonsense. + assertEq(frame.eval('new.target').throw.unsafeDereference().name, "SyntaxError"); + // assertEq(frame.eval('new.target').value.unsafeDereference(), expected); + } + }; + + g.eval("" + function f(d) { new g(d, g, 15); }); + + g.eval("" + function g(d, expected) { toggle(d, expected); }); + + g.eval("(" + function test() { + for (var i = 0; i < 5; i++) + f(false); + f(true); + } + ")();"); +}); diff --git a/js/src/jit-test/tests/debug/Frame-newTargetEval-02.js b/js/src/jit-test/tests/debug/Frame-newTargetEval-02.js new file mode 100644 index 000000000..3eb9114c5 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-newTargetEval-02.js @@ -0,0 +1,43 @@ +// Test that new.target is acceptably usable in RematerializedFrames. + +gczeal(0); + +load(libdir + "jitopts.js"); + +if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation)) + quit(); + +withJitOptions(Opts_Ion2NoOffthreadCompilation, function () { + var g = newGlobal(); + var dbg = new Debugger; + + g.toggle = function toggle(d, expected) { + if (d) { + dbg.addDebuggee(g); + + var frame = dbg.getNewestFrame(); + assertEq(frame.implementation, "ion"); + + // the arrow function will not be constructing, even though it has a + // new.target value. + assertEq(frame.constructing, false); + + // CONGRATS IF THIS FAILS! You, proud saviour, have made new.target parse + // in debug frame evals (presumably by hooking up static scope walks). + // Uncomment the assert below for efaust's undying gratitude. + // Note that we use .name here because of CCW nonsense. + assertEq(frame.eval('new.target').throw.unsafeDereference().name, "SyntaxError"); + // assertEq(frame.eval('new.target').return.unsafeDereference(), expected); + } + }; + + g.eval("" + function f(d) { new g(d, g, 15); }); + + g.eval("" + function g(d, expected) { (() => toggle(d, expected))(); }); + + g.eval("(" + function test() { + for (var i = 0; i < 5; i++) + f(false); + f(true); + } + ")();"); +}); diff --git a/js/src/jit-test/tests/debug/Frame-newTargetOverflow-01.js b/js/src/jit-test/tests/debug/Frame-newTargetOverflow-01.js new file mode 100644 index 000000000..1dd0b1bd4 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-newTargetOverflow-01.js @@ -0,0 +1,41 @@ +// Test that Ion frames are invalidated by turning on Debugger. Invalidation +// is unobservable, but we know that Ion scripts cannot handle Debugger +// handlers, so we test for the handlers being called. + +load(libdir + "jitopts.js"); + +if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation)) + quit(); + +// GCs can invalidate JIT code and cause this test to fail. +if ('gczeal' in this) + gczeal(0); + +withJitOptions(Opts_Ion2NoOffthreadCompilation, function () { + var g = newGlobal(); + var dbg = new Debugger; + + g.toggle = function toggle(d) { + if (d) { + dbg.addDebuggee(g); + + var frame = dbg.getNewestFrame(); + assertEq(frame.implementation, "ion"); + assertEq(frame.constructing, true); + + // overflow args are read from the parent's frame + // ensure we have the right offset to read from those. + assertEq(frame.arguments[1], 15); + } + }; + + g.eval("" + function f(d) { new g(d, 15); }); + + g.eval("" + function g(d) { toggle(d); }); + + g.eval("(" + function test() { + for (var i = 0; i < 5; i++) + f(false); + f(true); + } + ")();"); +}); diff --git a/js/src/jit-test/tests/debug/Frame-offset-01.js b/js/src/jit-test/tests/debug/Frame-offset-01.js new file mode 100644 index 000000000..e9bfbc810 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-offset-01.js @@ -0,0 +1,11 @@ +// frame.offset throws if !frame.live. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = Debugger(g); +var f; +dbg.onDebuggerStatement = function (frame) { f = frame; }; +g.eval("debugger;"); +assertEq(f.live, false); +assertThrowsInstanceOf(function () { f.offset; }, Error); diff --git a/js/src/jit-test/tests/debug/Frame-offset-02.js b/js/src/jit-test/tests/debug/Frame-offset-02.js new file mode 100644 index 000000000..344a11bfc --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-offset-02.js @@ -0,0 +1,16 @@ +// frame.offset gives different values at different points in a script. + +var g = newGlobal(); +var dbg = Debugger(g); +var s = undefined, a = [] +dbg.onDebuggerStatement = function (frame) { + if (s === undefined) + s = frame.script; + else + assertEq(s, frame.script); + assertEq(frame.offset !== undefined, true); + assertEq(a.indexOf(frame.offset), -1); + a.push(frame.offset); +}; +g.eval("debugger; debugger; debugger;"); +assertEq(a.length, 3); diff --git a/js/src/jit-test/tests/debug/Frame-older-01.js b/js/src/jit-test/tests/debug/Frame-older-01.js new file mode 100644 index 000000000..1a21d1b44 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-older-01.js @@ -0,0 +1,19 @@ +// Basic call chain. + +var g = newGlobal(); +var result = null; +var dbg = new Debugger(g); +dbg.onDebuggerStatement = function (frame) { + var a = []; + assertEq(frame === frame.older, false); + for (; frame; frame = frame.older) + a.push(frame.type === 'call' ? frame.callee.name : frame.type); + a.reverse(); + result = a.join(", "); +}; + +g.eval("function first() { return second(); }"); +g.eval("function second() { return eval('third()'); }"); +g.eval("function third() { debugger; }"); +g.evaluate("first();"); +assertEq(result, "global, first, second, eval, third"); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-01.js b/js/src/jit-test/tests/debug/Frame-onPop-01.js new file mode 100644 index 000000000..c459fd71c --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-01.js @@ -0,0 +1,29 @@ +// When multiple frames have onPop handlers, they are called in the correct order. +var g = newGlobal(); +g.eval("function f() { debugger; }"); +g.eval("function g() { f(); }"); +g.eval("function h() { g(); }"); +g.eval("function i() { h(); }"); + +var dbg = new Debugger(g); +var log; +function logger(frame, mark) { + return function (completion) { + assertEq(this, frame); + assertEq('return' in completion, true); + log += mark; + }; +} +dbg.onEnterFrame = function handleEnter(f) { + log += "(" + f.callee.name; + // Note that this establishes a distinct function object as each + // frame's onPop handler. Thus, a pass proves that each frame is + // remembering its handler separately. + f.onPop = logger(f, f.callee.name + ")"); +}; +dbg.onDebuggerStatement = function handleDebugger(f) { + log += 'd'; +}; +log = ''; +g.i(); +assertEq(log, "(i(h(g(fdf)g)h)i)"); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-02.js b/js/src/jit-test/tests/debug/Frame-onPop-02.js new file mode 100644 index 000000000..28d9cef8b --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-02.js @@ -0,0 +1,20 @@ +// Clearing a frame's onPop handler works. +var g = newGlobal(); +g.eval("function f() { debugger; }"); +var dbg = new Debugger(g); + +var log; +dbg.onEnterFrame = function handleEnter(f) { + log += "("; + f.onPop = function handlePop() { + assertEq("handlePop was called", "handlePop should never be called"); + }; +}; +dbg.onDebuggerStatement = function handleDebugger(f) { + log += "d"; + assertEq(typeof f.onPop, "function"); + f.onPop = undefined; +}; +log = ''; +g.f(); +assertEq(log, "(d"); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-03.js b/js/src/jit-test/tests/debug/Frame-onPop-03.js new file mode 100644 index 000000000..dfd4dfcaf --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-03.js @@ -0,0 +1,32 @@ +// When an exception is propagated out of multiple frames, their onPop +// and onExceptionUnwind handlers are called in the correct order. +var g = newGlobal(); +g.eval("function f() { throw 'mud'; }"); +g.eval("function g() { f(); }"); +g.eval("function h() { g(); }"); +g.eval("function i() { h(); }"); + +var dbg = new Debugger(g); +var log; +function makePopHandler(label) { + return function handlePop(completion) { + log += label; + assertEq(completion.throw, "mud"); + }; +} +dbg.onEnterFrame = function handleEnter(f) { + log += "(" + f.callee.name; + f.onPop = makePopHandler(")" + f.callee.name); +}; +dbg.onExceptionUnwind = function handleExceptionUnwind(f, x) { + assertEq(x, 'mud'); + log += "u" + f.callee.name; +}; +log = ''; +try { + g.i(); +} catch (x) { + log += 'c'; + assertEq(x, "mud"); +} +assertEq(log, "(i(h(g(fuf)fug)guh)hui)ic"); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-04.js b/js/src/jit-test/tests/debug/Frame-onPop-04.js new file mode 100644 index 000000000..6b3adc1c7 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-04.js @@ -0,0 +1,30 @@ +// When a termination is propagated out of multiple frames, their onPop +// handlers are called in the correct order, and no onExceptionUnwind +// handlers are called. +var g = newGlobal(); +g.eval("function f() { terminate(); }"); +g.eval("function g() { f(); }"); +g.eval("function h() { g(); }"); +g.eval("function i() { h(); }"); + +var dbg = new Debugger(g); +var log; +var count = 0; +function makePopHandler(label, resumption) { + return function handlePop(completion) { + log += label; + assertEq(completion, null); + return resumption; + }; +} +dbg.onEnterFrame = function handleEnter(f) { + log += "(" + f.callee.name; + f.onPop = makePopHandler(f.callee.name + ")", + count++ == 0 ? { return: 'king' } : undefined); +}; +dbg.onExceptionUnwind = function handleExceptionUnwind(f, x) { + log += 'u'; +}; +log = ''; +assertEq(g.i(), 'king'); +assertEq(log, "(i(h(g(ff)g)h)i)"); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-05.js b/js/src/jit-test/tests/debug/Frame-onPop-05.js new file mode 100644 index 000000000..a4bc22f00 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-05.js @@ -0,0 +1,25 @@ +var g = newGlobal(); +var dbg = new Debugger(g); +g.debuggerGlobal = this; +var log; + +dbg.onEnterFrame = function handleEnter(f) { + log += '('; + f.onPop = function handlePop(c) { + log += ')'; + assertEq(c.throw, "election"); + }; +}; +dbg.onExceptionUnwind = function handleExceptionUnwind(f, x) { + log += 'u'; + assertEq(x, "election"); +}; + +log = ''; +try { + g.eval("try { throw 'election'; } finally { debuggerGlobal.log += 'f'; }"); +} catch (x) { + log += 'c'; + assertEq(x, 'election'); +} +assertEq(log, '(ufu)c'); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-06.js b/js/src/jit-test/tests/debug/Frame-onPop-06.js new file mode 100644 index 000000000..f627e248b --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-06.js @@ -0,0 +1,19 @@ +// dbg.getNewestFrame in an onPop handler returns the frame being popped. +var g = newGlobal(); +g.eval("function f() { debugger; }"); +g.eval("function g() { f(); }"); +g.eval("function h() { g(); }"); +g.eval("function i() { h(); }"); + +var dbg = new Debugger(g); +var log; +dbg.onEnterFrame = function handleEnter(f) { + log += "(" + f.callee.name; + f.onPop = function handlePop(c) { + log += ")" + f.callee.name; + assertEq(dbg.getNewestFrame(), this); + }; +}; +log = ''; +g.i(); +assertEq(log, "(i(h(g(f)f)g)h)i"); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-07.js b/js/src/jit-test/tests/debug/Frame-onPop-07.js new file mode 100644 index 000000000..5ef226b5a --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-07.js @@ -0,0 +1,30 @@ +// Trying to set an onPop handler on a dead frame throws an exception. +var g = newGlobal(); +g.eval("function f() { }"); +g.eval("function g() { f(); }"); +g.eval("function h() { g(); }"); +g.eval("function i() { h(); }"); +var dbg = new Debugger(g); +var log; + +var frames = []; +dbg.onEnterFrame = function handleEnter(f) { + log += "("; + assertEq(f.live, true); + frames.push(f); +}; +log = ''; +g.i(); +assertEq(log, "(((("); +assertEq(frames.length, 4); +for (i = 0; i < frames.length; i++) { + assertEq(frames[i].live, false); + var set = false; + try { + frames[i].onPop = function unappreciated() { }; + set = true; // don't assert in a 'try' block + } catch (x) { + assertEq(x instanceof Error, true); + } + assertEq(set, false); +} diff --git a/js/src/jit-test/tests/debug/Frame-onPop-08.js b/js/src/jit-test/tests/debug/Frame-onPop-08.js new file mode 100644 index 000000000..683948a6e --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-08.js @@ -0,0 +1,16 @@ +// Setting onPop handlers from a 'debugger' statement handler works. +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +dbg.onDebuggerStatement = function handleDebugger(frame) { + assertEq(frame.type, "eval"); + log += 'd'; + frame.onPop = function handlePop(c) { + log += ')'; + }; +}; + +log = ''; +g.eval('debugger;'); +assertEq(log, 'd)'); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-09.js b/js/src/jit-test/tests/debug/Frame-onPop-09.js new file mode 100644 index 000000000..abdd3c29d --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-09.js @@ -0,0 +1,23 @@ +// Setting onPop handlers from an onExceptionUnwind handler works. +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +dbg.onExceptionUnwind = function handleUnwind(frame) { + log += 'u'; + assertEq(frame.type, "eval"); + frame.onPop = function handleCallPop(c) { + log += ')'; + assertEq(c.throw, 'up'); + }; +}; + +log = ""; +try { + g.eval("throw 'up';"); + log += '-'; +} catch (x) { + log += 'c'; + assertEq(x, 'up'); +} +assertEq(log, 'u)c'); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-10.js b/js/src/jit-test/tests/debug/Frame-onPop-10.js new file mode 100644 index 000000000..9184929a2 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-10.js @@ -0,0 +1,22 @@ +// Setting onPop handlers from an onStep handler works. +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +dbg.onDebuggerStatement = function handleDebugger(frame) { + log += 'd'; + assertEq(frame.type, "eval"); + frame.onStep = function handleStep() { + log += 's'; + this.onStep = undefined; + this.onPop = function handlePop() { + log += ')'; + }; + }; +}; + +log = ""; +g.flag = false; +g.eval('debugger; flag = true'); +assertEq(log, 'ds)'); +assertEq(g.flag, true); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-11.js b/js/src/jit-test/tests/debug/Frame-onPop-11.js new file mode 100644 index 000000000..83c60aec7 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-11.js @@ -0,0 +1,23 @@ +// Setting onPop handlers from breakpoint handlers works. +var g = newGlobal(); +g.eval("function f(){ return 'to normalcy'; }"); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); +var log; + +// Set a breakpoint at the start of g.f +var gf = gw.makeDebuggeeValue(g.f); +var fStartOffset = gf.script.getLineOffsets(gf.script.startLine)[0]; +gf.script.setBreakpoint(fStartOffset, { + hit: function handleHit(frame) { + log += 'b'; + frame.onPop = function handlePop(c) { + log += ')'; + assertEq(c.return, "to normalcy"); + }; + } +}); + +log = ""; +assertEq(g.f(), "to normalcy"); +assertEq(log, "b)"); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-12.js b/js/src/jit-test/tests/debug/Frame-onPop-12.js new file mode 100644 index 000000000..044c23b6d --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-12.js @@ -0,0 +1,21 @@ +// Setting an onPop handler from an onPop handler doesn't throw, but the +// new handler doesn't fire. +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +dbg.onDebuggerStatement = function handleDebugger(frame) { + log += 'd'; + assertEq(frame.type, "eval"); + frame.onPop = function firstHandlePop(c) { + log +=')'; + assertEq(c.return, 'on investment'); + this.onPop = function secondHandlePop(c) { + assertEq("secondHandlePop was called", "secondHandlePop should never be called"); + }; + }; +}; + +log = ""; +g.eval("debugger; 'on investment';"); +assertEq(log, 'd)'); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-13.js b/js/src/jit-test/tests/debug/Frame-onPop-13.js new file mode 100644 index 000000000..45ebcc65e --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-13.js @@ -0,0 +1,37 @@ +// One can set onPop handlers on some frames but not others. +var g = newGlobal(); +g.eval("function f(n) { if (n > 0) f(n-1); else debugger; }"); +var dbg = new Debugger(g); +var log; + +Debugger.Frame.prototype.nthOlder = function nthOlder(n) { + var f = this; + while (n-- > 0) + f = f.older; + return f; +}; + +dbg.onEnterFrame = function handleEnter(f) { + log += "(" + f.arguments[0]; +}; + +function makePopHandler(n) { + return function handlePop(c) { + log += ")" + this.arguments[0]; + assertEq(this.arguments[0], n); + }; +} + +dbg.onDebuggerStatement = function handleDebugger(f) { + // Set onPop handers on some frames, and leave others alone. Vary the + // spacing. + f.nthOlder(2).onPop = makePopHandler(2); + f.nthOlder(3).onPop = makePopHandler(3); + f.nthOlder(5).onPop = makePopHandler(5); + f.nthOlder(8).onPop = makePopHandler(8); + f.nthOlder(13).onPop = makePopHandler(13); +}; + +log = ''; +g.f(20); +assertEq(log, "(20(19(18(17(16(15(14(13(12(11(10(9(8(7(6(5(4(3(2(1(0)2)3)5)8)13"); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-14.js b/js/src/jit-test/tests/debug/Frame-onPop-14.js new file mode 100644 index 000000000..82b5c9ec8 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-14.js @@ -0,0 +1,25 @@ +// A frame's onPop handler is called only once, even if it is for a function +// called from a loop. +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +var count; +dbg.onDebuggerStatement = function handleDebug(frame) { + log += 'd'; + assertEq(frame.type, "call"); + count++; + if (count == 10) { + frame.onPop = function handlePop(c) { + log += ')' + this.arguments[0]; + assertEq(c.return, "snifter"); + }; + } +}; + +g.eval("function f(n) { debugger; return 'snifter'; }"); +log = ''; +count = 0; +g.eval("for (i = 0; i < 20; i++) f(i);"); +assertEq(count, 20); +assertEq(log, "dddddddddd)9dddddddddd"); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-15.js b/js/src/jit-test/tests/debug/Frame-onPop-15.js new file mode 100644 index 000000000..d76066ad7 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-15.js @@ -0,0 +1,32 @@ +// Each resumption of a generator gets a fresh frame, whose onPop handler +// fires the next time the generator yields. +// This is not the behavior the spec requests, but it's what we do for the +// moment, and it's good to check that at least we don't crash. +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +var debuggerFrames = []; +var poppedFrames = []; +dbg.onDebuggerStatement = function handleDebugger(frame) { + log += 'd'; + assertEq(frame.type, "call"); + + assertEq(debuggerFrames.indexOf(frame), -1); + assertEq(poppedFrames.indexOf(frame), -1); + debuggerFrames.push(frame); + + if (frame.eval('i').return % 3 == 0) { + frame.onPop = function handlePop(c) { + log += ')' + c.return; + assertEq(debuggerFrames.indexOf(this) != -1, true); + assertEq(poppedFrames.indexOf(this), -1); + poppedFrames.push(this); + }; + } +}; + +g.eval("function g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }"); +log =''; +assertEq(g.eval("var t = 0; for (j in g()) t += j; t;"), 45); +assertEq(log, "d)0ddd)3ddd)6ddd)9"); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-16.js b/js/src/jit-test/tests/debug/Frame-onPop-16.js new file mode 100644 index 000000000..a1bdf71b6 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-16.js @@ -0,0 +1,18 @@ +// onPop handlers fire even on frames that make tail calls. +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +g.eval('function f(n) { if (n > 0) f(n-1); else debugger; }'); + +dbg.onEnterFrame = function handleEnter(frame) { + log += '('; + frame.onPop = function handlePop(c) { + log += ')'; + assertEq(typeof c == "object" && 'return' in c, true); + }; +}; + +log = ''; +g.f(10); +assertEq(log, "((((((((((()))))))))))"); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-17.js b/js/src/jit-test/tests/debug/Frame-onPop-17.js new file mode 100644 index 000000000..5cb382403 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-17.js @@ -0,0 +1,41 @@ +// onPop surfaces. +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = new Debugger(g); + +// Assigning a bogus value to Debugger.Frame.prototype.onPop raises a TypeError. +function test(badValue) { + print("store " + uneval(badValue) + " in Debugger.Frame.prototype.onPop"); + + var log; + dbg.onDebuggerStatement = function handleDebugger(frame) { + log += "d"; + assertThrowsInstanceOf(function () { frame.onPop = badValue; }, TypeError); + }; + + log = ""; + g.eval("debugger"); + assertEq(log, "d"); +} + +test(null); +test(false); +test(1); +test("stringy"); +test(Symbol("symbolic")); +test({}); +test([]); + +// Getting and setting the prototype's onPop is an error. +assertThrowsInstanceOf(function () { Debugger.Frame.prototype.onPop; }, TypeError); +assertThrowsInstanceOf( + function () { Debugger.Frame.prototype.onPop = function () {}; }, + TypeError); + +// The getters and setters correctly check the type of their 'this' argument. +var descriptor = Object.getOwnPropertyDescriptor(Debugger.Frame.prototype, 'onPop'); +assertEq(descriptor.configurable, true); +assertEq(descriptor.enumerable, false); +assertThrowsInstanceOf(function () { descriptor.get.call({}); }, TypeError); +assertThrowsInstanceOf(function () { descriptor.set.call({}, function() {}); }, TypeError); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-18.js b/js/src/jit-test/tests/debug/Frame-onPop-18.js new file mode 100644 index 000000000..0e04de6f4 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-18.js @@ -0,0 +1,22 @@ +// A garbage collection in the debugger compartment does not disturb onPop +// handlers. +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +dbg.onEnterFrame = function handleEnter(frame) { + log += '('; + frame.onPop = function handlePop(completion) { + log += ')'; + }; +}; + +dbg.onDebuggerStatement = function handleDebugger (frame) { + log += 'd'; + // GC in the debugger's compartment only. + gc(dbg); +}; + +log = ''; +assertEq(g.eval('debugger; 42;'), 42); +assertEq(log, '(d)'); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-19.js b/js/src/jit-test/tests/debug/Frame-onPop-19.js new file mode 100644 index 000000000..0df4154d6 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-19.js @@ -0,0 +1,16 @@ +// A garbage collection in the debuggee compartment does not disturb onPop +// handlers. +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +dbg.onEnterFrame = function handleEnter(frame) { + log += '('; + frame.onPop = function handlePop(completion) { + log += ')'; + }; +}; + +log = ''; +assertEq(g.eval('gc(this); 42;'), 42); +assertEq(log, '()'); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-20.js b/js/src/jit-test/tests/debug/Frame-onPop-20.js new file mode 100644 index 000000000..5da104e32 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-20.js @@ -0,0 +1,15 @@ +// A global garbage collection does not disturb onPop handlers. +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +dbg.onEnterFrame = function handleEnter(frame) { + log += '('; + frame.onPop = function handlePop(completion) { + log += ')'; + }; +}; + +log = ''; +assertEq(g.eval('gc(); 42;'), 42); +assertEq(log, '()'); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-21.js b/js/src/jit-test/tests/debug/Frame-onPop-21.js new file mode 100644 index 000000000..2cff89740 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-21.js @@ -0,0 +1,30 @@ +// frame.eval works from an onPop handler. +var g = newGlobal(); +g.eval('function f(a,b) { var x = "entablature", y; debugger; return x+y+a+b; }'); + +var dbg = new Debugger(g); +var log; + +dbg.onDebuggerStatement = function handleDebugger(frame) { + log += 'd'; + frame.onPop = handlePop; +}; + +function handlePop(c) { + log += ')'; + + // Arguments must be live. + assertEq(this.eval('a').return, 'frieze'); + assertEq(this.eval('b = "architrave"').return, 'architrave'); + assertEq(this.eval('arguments[1]').return, 'architrave'); + assertEq(this.eval('b').return, 'architrave'); + + // function-scope variables must be live. + assertEq(this.eval('x').return, 'entablature'); + assertEq(this.eval('y = "cornice"').return, 'cornice'); + assertEq(this.eval('y').return, 'cornice'); +} + +log = ''; +g.eval('f("frieze", "stylobate")'); +assertEq(log, 'd)'); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-23.js b/js/src/jit-test/tests/debug/Frame-onPop-23.js new file mode 100644 index 000000000..d49e1d456 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-23.js @@ -0,0 +1,34 @@ +// Check that the line number reported at an onPop stop makes sense, +// even when it happens on an "artificial" instruction. + +var g = newGlobal(); + +// This bit of code arranges for the line number of the "artificial" +// instruction to be something nonsensical -- the middle of a loop +// which cannot be entered. +g.eval(`function f() { + debugger; // +0 + if(false) { // +1 + for(var b=0; b<0; b++) { // +2 + c = 2; // +3 + } // +4 + } // +5 +} // +6 +`); + +var dbg = Debugger(g); + +let debugLine; +let foundLine; + +dbg.onDebuggerStatement = function(frame) { + debugLine = frame.script.getOffsetLocation(frame.offset).lineNumber; + frame.onPop = function(c) { + foundLine = this.script.getOffsetLocation(this.offset).lineNumber; + }; +}; + +g.eval("f();\n"); + +// The stop should happen on the closing brace of the function. +assertEq(foundLine == debugLine + 6, true); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-after-debugger-return.js b/js/src/jit-test/tests/debug/Frame-onPop-after-debugger-return.js new file mode 100644 index 000000000..4e811a871 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-after-debugger-return.js @@ -0,0 +1,11 @@ +// Bug 744730. + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (f) { return {return: 1234}; }; +var hit = false; +dbg.onEnterFrame = function (f) { + f.onPop = function () { hit = true}; +}; +g.eval("debugger;"); +assertEq(hit, true); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-disabled.js b/js/src/jit-test/tests/debug/Frame-onPop-disabled.js new file mode 100644 index 000000000..f02e41f23 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-disabled.js @@ -0,0 +1,44 @@ +// An onPop handler in a disabled Debugger's frame shouldn't fire. + +var g = newGlobal(); +var dbg = new Debugger(g); +g.eval('function f() { debugger; }'); +var log; +dbg.onEnterFrame = function handleEnterFrame(f) { + log += '('; + assertEq(f.callee.name, 'f'); + f.onPop = function handlePop(c) { + log += ')'; + assertEq(dbg.enabled, true); + }; +}; + +var enable; +dbg.onDebuggerStatement = function handleDebugger(f) { + dbg.enabled = enable; +} + + +// This should fire the onEnterFrame and onPop handlers. +log = 'a'; +enable = true; +g.f(); + +// This should fire the onEnterFrame handler, but not the onPop. +log += 'b'; +enable = false; +g.f(); + +// This should fire neither. +log += 'c'; +dbg.enabled = false; +enable = false; +g.f(); + +// This should fire both again. +log += 'd'; +dbg.enabled = true; +enable = true; +g.f(); + +assertEq(log, 'a()b(cd()'); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-error-error.js b/js/src/jit-test/tests/debug/Frame-onPop-error-error.js new file mode 100644 index 000000000..b17dac421 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-error-error.js @@ -0,0 +1,60 @@ +// |jit-test| error: TestComplete +// onPop can request a termination when stopped for a termination + +var g = newGlobal(); +var dbg = new Debugger(g); + +// We use Debugger.Frame.prototype.eval and ignore the outer 'eval' frame so we +// can catch the termination. + +function test(type, provocation) { + // Help people figure out which 'test' call failed. + print("type: " + uneval(type)); + print("provocation: " + uneval(provocation)); + + var log; + dbg.onEnterFrame = function handleFirstFrame(f) { + log += 'f'; + dbg.onDebuggerStatement = function handleDebugger(f) { + log += 'd'; + return null; + }; + + dbg.onEnterFrame = function handleSecondFrame(f) { + log += 'e'; + assertEq(f.type, 'eval'); + + dbg.onEnterFrame = function handleThirdFrame(f) { + log += '('; + assertEq(f.type, type); + + dbg.onEnterFrame = function handleExtraFrames(f) { + // This should never be called. + assertEq(false, true); + }; + + f.onPop = function handlePop(c) { + log += ')'; + assertEq(c, null); + return null; + }; + }; + }; + + assertEq(f.eval(provocation), null); + }; + + log = ''; + // This causes handleFirstFrame to be called. + assertEq(typeof g.eval('eval'), 'function'); + assertEq(log, 'fe(d)'); + + print(); +} + +g.eval('function f() { debugger; return \'termination fail\'; }'); +test('call', 'f();'); +test('call', 'new f;'); +test('eval', 'eval(\'debugger; \\\'termination fail\\\';\');'); +test('global', 'evaluate(\'debugger; \\\'termination fail\\\';\');'); +throw 'TestComplete'; diff --git a/js/src/jit-test/tests/debug/Frame-onPop-error-return.js b/js/src/jit-test/tests/debug/Frame-onPop-error-return.js new file mode 100644 index 000000000..f0ad9baea --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-error-return.js @@ -0,0 +1,47 @@ +// |jit-test| error: TestComplete +// onPop can change a termination into a normal return. + +var g = newGlobal(); +var dbg = new Debugger(g); + +function test(type, provocation) { + var log; + var wasConstructing; + + // Help people figure out which 'test' call failed. + print("type: " + uneval(type)); + print("provocation: " + uneval(provocation)); + + dbg.onDebuggerStatement = function handleDebuggerStatement(f) { + log += 'd'; + return null; + }; + + dbg.onEnterFrame = function handleEnterFrame(f) { + log += '('; + assertEq(f.type, type); + wasConstructing = f.constructing; + f.onPop = function handlePop(c) { + log += ')'; + assertEq(c, null); + return { return: 'favor' }; + }; + }; + + log = ''; + var result = provocation(); + if (wasConstructing) + assertEq(typeof result, "object"); + else + assertEq(result, 'favor'); + assertEq(log, "(d)"); + + print(); +} + +g.eval("function f() { debugger; return 'termination fail'; }"); +test("call", g.f); +test("call", function () { return new g.f; }); +test("eval", function () { return g.eval("debugger; \'termination fail\';"); }); +test("global", function () { return g.evaluate("debugger; \'termination fail\';"); }); +throw 'TestComplete'; diff --git a/js/src/jit-test/tests/debug/Frame-onPop-error-scope-unwind-01.js b/js/src/jit-test/tests/debug/Frame-onPop-error-scope-unwind-01.js new file mode 100644 index 000000000..9c7b4f92e --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-error-scope-unwind-01.js @@ -0,0 +1,33 @@ +// Tests that exception handling works with block scopes. + +var g = newGlobal(); +var dbg = new Debugger(g); +var correct; +dbg.onEnterFrame = function (f) { + if (f.callee && f.callee.name == "f") { + f.onPop = function() { + // The scope at the point of onPop is at the point of popping (the + // noSuchFn call). + correct = (f.environment.getVariable("e") === 42 && + f.environment.getVariable("outer") === undefined); + }; + } +}; +g.eval("" + function f() { + var outer = 43; + try { + eval(""); + throw 42; + } catch (e) { + noSuchFn(e); + } +}); + + +try { + g.eval("f();"); +} catch (e) { + // The above is expected to throw. +} + +assertEq(correct, true); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-error-scope-unwind-02.js b/js/src/jit-test/tests/debug/Frame-onPop-error-scope-unwind-02.js new file mode 100644 index 000000000..157dbf5bb --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-error-scope-unwind-02.js @@ -0,0 +1,36 @@ +// Tests that exception handling works with block scopes. + +var g = newGlobal(); +var dbg = new Debugger(g); +var correct; +dbg.onEnterFrame = function (f) { + if (f.callee && f.callee.name == "f") { + f.onPop = function() { + // The scope at the point of onPop is at the point of popping (the + // noSuchFn call). + correct = (f.environment.getVariable("e") === 42 && + f.environment.getVariable("outer") === undefined); + }; + } +}; +g.eval("" + function f() { + var outer = 43; + // Surround with a loop to insert a loop trynote. + for (;;) { + try { + eval(""); + throw 42; + } catch (e) { + noSuchFn(e); + } + } +}); + + +try { + g.eval("f();"); +} catch (e) { + // The above is expected to throw. +} + +assertEq(correct, true); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-error-throw.js b/js/src/jit-test/tests/debug/Frame-onPop-error-throw.js new file mode 100644 index 000000000..1fabb6ff8 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-error-throw.js @@ -0,0 +1,42 @@ +// |jit-test| error: TestComplete +// onPop can change a termination into a throw. + +load(libdir + "asserts.js"); +var g = newGlobal(); +var dbg = new Debugger(g); + +function test(type, provocation) { + var log; + + // Help people figure out which 'test' call failed. + print("type: " + uneval(type)); + print("provocation: " + uneval(provocation)); + + dbg.onDebuggerStatement = function handleDebuggerStatement(f) { + log += 'd'; + return null; + }; + + dbg.onEnterFrame = function handleEnterFrame(f) { + log += '('; + assertEq(f.type, type); + f.onPop = function handlePop(c) { + log += ')'; + assertEq(c, null); + return { throw: 'snow' }; + }; + }; + + log = ''; + assertThrowsValue(provocation, 'snow'); + assertEq(log, "(d)"); + + print(); +} + +g.eval("function f() { debugger; return 'termination fail'; }"); +test("call", g.f); +test("call", function () { return new g.f; }); +test("eval", function () { return g.eval("debugger; \'termination fail\';"); }); +test("global", function () { return g.evaluate("debugger; \'termination fail\';"); }); +throw 'TestComplete'; diff --git a/js/src/jit-test/tests/debug/Frame-onPop-error.js b/js/src/jit-test/tests/debug/Frame-onPop-error.js new file mode 100644 index 000000000..0358ca701 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-error.js @@ -0,0 +1,59 @@ +// |jit-test| error: TestComplete +// onPop fires when frames are terminated. + +var g = newGlobal(); +var dbg = new Debugger(g); + +// We use Debugger.Frame.prototype.eval and ignore the outer 'eval' frame so we +// can catch the termination. + +function test(type, provocation) { + // Help people figure out which 'test' call failed. + print("type: " + uneval(type)); + print("provocation: " + uneval(provocation)); + + var log; + dbg.onEnterFrame = function handleFirstFrame(f) { + log += 'f'; + dbg.onDebuggerStatement = function handleDebugger(f) { + log += 'd'; + return null; + }; + + dbg.onEnterFrame = function handleSecondFrame(f) { + log += 'e'; + assertEq(f.type, 'eval'); + + dbg.onEnterFrame = function handleThirdFrame(f) { + log += '('; + assertEq(f.type, type); + + dbg.onEnterFrame = function handleExtraFrames(f) { + // This should never be called. + assertEq(false, true); + }; + + f.onPop = function handlePop(c) { + log += ')'; + assertEq(c, null); + }; + }; + }; + + assertEq(f.eval(provocation), null); + }; + + log = ''; + // This causes handleFirstFrame to be called. + assertEq(typeof g.eval('eval'), 'function'); + assertEq(log, 'fe(d)'); + + print(); +} + +g.eval('function f() { debugger; return \'termination fail\'; }'); +test('call', 'f();'); +test('call', 'new f;'); +test('eval', 'eval(\'debugger; \\\'termination fail\\\';\');'); +test('global', 'evaluate(\'debugger; \\\'termination fail\\\';\');'); +throw 'TestComplete'; diff --git a/js/src/jit-test/tests/debug/Frame-onPop-generators-01.js b/js/src/jit-test/tests/debug/Frame-onPop-generators-01.js new file mode 100644 index 000000000..ebb228c5b --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-generators-01.js @@ -0,0 +1,21 @@ +// |jit-test| error: StopIteration +// Returning {throw:} from an onPop handler when yielding works and +// does closes the generator-iterator. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +dbg.onDebuggerStatement = function handleDebugger(frame) { + frame.onPop = function (c) { + return {throw: "fit"}; + }; +}; +g.eval("function g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }"); +g.eval("var it = g();"); +var rv = gw.executeInGlobal("it.next();"); +assertEq(rv.throw, "fit"); + +dbg.enabled = false; +g.it.next(); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-generators-02.js b/js/src/jit-test/tests/debug/Frame-onPop-generators-02.js new file mode 100644 index 000000000..2962e6058 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-generators-02.js @@ -0,0 +1,19 @@ +// |jit-test| error: fit + +// Throwing an exception from an onPop handler when yielding terminates the debuggee +// but does not close the generator-iterator. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +dbg.onDebuggerStatement = function handleDebugger(frame) { + frame.onPop = function (c) { + throw "fit"; + }; +}; +g.eval("function g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }"); +g.eval("var it = g();"); +assertEq(gw.executeInGlobal("it.next();"), null); + +dbg.enabled = false; +assertEq(g.it.next(), 1); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-multiple-01.js b/js/src/jit-test/tests/debug/Frame-onPop-multiple-01.js new file mode 100644 index 000000000..87e23825f --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-multiple-01.js @@ -0,0 +1,127 @@ +// Multiple debuggers all get their onPop handlers called, and see each others' effects. + +function completionsEqual(c1, c2) { + if (c1 && c2) { + if (c1.throw) + return c1.throw === c2.throw; + else + return c1.return === c2.return; + } + return c1 === c2; +} + +function completionString(c) { + if (c == null) + return 'x'; + if (c.return) + return 'r' + c.return; + if (c.throw) + return 't' + c.throw; + return '?'; +} + +var g = newGlobal(); // poor thing +g.eval('function f() { debugger; return "1"; }'); + +// We create a bunch of debuggers, but they all consult this global variable +// for expectations and responses, so the order in which events get +// reported to the debuggers doesn't matter. +// +// This list includes every pair of transitions, and is of minimal length. +// As if opportunity cost were just some theoretical concern. +var sequence = [{ expect: { return: '1' }, resume: { return: '2'} }, + { expect: { return: '2' }, resume: { throw: '3'} }, + { expect: { throw: '3' }, resume: { return: '4'} }, + { expect: { return: '4' }, resume: null }, + { expect: null, resume: { throw: '5'} }, + { expect: { throw: '5' }, resume: { throw: '6'} }, + { expect: { throw: '6' }, resume: null }, + { expect: null, resume: null }, + { expect: null, resume: { return: '7'} }]; + +// A list of the debuggers' Debugger.Frame instances. When it's all over, +// we test that they are all marked as no longer live. +var frames = []; + +// We start off the test via Debugger.Frame.prototype.eval, so if we end +// with a termination, we still catch it, instead of aborting the whole +// test. (Debugger.Object.prototype.executeInGlobal would simplify this...) +var dbg0 = new Debugger(g); +dbg0.onEnterFrame = function handleOriginalEnter(frame) { + dbg0.log += '('; + dbg0.onEnterFrame = undefined; + + assertEq(frame.live, true); + frames.push(frame); + + var dbgs = []; + var log; + + // Create a separate debugger to carry out each item in sequence. + for (s in sequence) { + // Each debugger's handlers close over a distinct 'dbg', but + // that's the only distinction between them. Otherwise, they're + // driven entirely by global data, so the order in which events are + // dispatched to them shouldn't matter. + let dbg = new Debugger(g); + dbgs.push(dbg); + + dbg.onDebuggerStatement = function handleDebuggerStatement(f) { + log += 'd'; + assertEq(f.live, true); + frames.push(f); + }; + + // First expect the 'eval'... + dbg.onEnterFrame = function handleEnterEval(f) { + log += 'e'; + assertEq(f.type, 'eval'); + assertEq(f.live, true); + frames.push(f); + + // Then expect the call. + dbg.onEnterFrame = function handleEnterCall(f) { + log += '('; + assertEq(f.type, 'call'); + assertEq(f.live, true); + frames.push(f); + + // Don't expect any further frames. + dbg.onEnterFrame = function handleExtraEnter(f) { + log += 'z'; + }; + + f.onPop = function handlePop(c) { + log += ')' + completionString(c); + assertEq(this.live, true); + frames.push(this); + + // Check that this debugger is in the list, and then remove it. + var i = dbgs.indexOf(dbg); + assertEq(i != -1, true); + dbgs.splice(i,1); + + // Check the frame's completion value against 'sequence'. + assertEq(completionsEqual(c, sequence[0].expect), true); + + // Provide the next resumption value from 'sequence'. + return sequence.shift().resume; + }; + }; + }; + } + + log = ''; + assertEq(completionsEqual(frame.eval('f()'), { return: '7' }), true); + assertEq(log, "eeeeeeeee(((((((((ddddddddd)r1)r2)t3)r4)x)t5)t6)x)x"); + + dbg0.log += '.'; +}; + +dbg0.log = ''; +g.eval('eval'); +assertEq(dbg0.log, '(.'); + +// Check that all Debugger.Frame instances we ran into are now marked as dead. +for (var i = 0; i < frames.length; i++) + assertEq(frames[i].live, false); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-multiple-02.js b/js/src/jit-test/tests/debug/Frame-onPop-multiple-02.js new file mode 100644 index 000000000..791b27752 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-multiple-02.js @@ -0,0 +1,36 @@ +// One Debugger's onPop handler can remove another Debugger's onPop handler. +var g = newGlobal(); +var dbg1 = new Debugger(g); +var dbg2 = new Debugger(g); + +var log; +var frames = []; +var firstPop = true; + +function handleEnter(frame) { + log += '('; + frames.push(frame); + frame.onPop = function handlePop(completion) { + log += ')'; + assertEq(completion.return, 42); + if (firstPop) { + // We can't say which frame's onPop handler will get called first. + if (this == frames[0]) + frames[1].onPop = undefined; + else + frames[0].onPop = undefined; + gc(); + } else { + assertEq("second pop handler was called", + "second pop handler should not be called"); + } + firstPop = false; + }; +}; + +dbg1.onEnterFrame = handleEnter; +dbg2.onEnterFrame = handleEnter; + +log = ''; +assertEq(g.eval('40 + 2'), 42); +assertEq(log, '(()'); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-multiple-03.js b/js/src/jit-test/tests/debug/Frame-onPop-multiple-03.js new file mode 100644 index 000000000..5e9d1411d --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-multiple-03.js @@ -0,0 +1,36 @@ +// One Debugger's onPop handler can disable another Debugger. +var g = newGlobal(); +var dbg1 = new Debugger(g); +var dbg2 = new Debugger(g); + +var log; +var frames = []; +var firstPop = true; + +function handleEnter(frame) { + log += '('; + frames.push(frame); + frame.debugger = this; + frame.onPop = function handlePop(completion) { + log += ')'; + assertEq(completion.return, 42); + if (firstPop) { + // We can't say which frame's onPop handler will get called first. + if (this == frames[0]) + frames[1].debugger.enabled = false; + else + frames[0].debugger.enabled = false; + } else { + assertEq("second pop handler was called", + "second pop handler should not be called"); + } + firstPop = false; + }; +}; + +dbg1.onEnterFrame = handleEnter; +dbg2.onEnterFrame = handleEnter; + +log = ''; +assertEq(g.eval('40 + 2'), 42); +assertEq(log, '(()'); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-multiple-04.js b/js/src/jit-test/tests/debug/Frame-onPop-multiple-04.js new file mode 100644 index 000000000..953e231ad --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-multiple-04.js @@ -0,0 +1,27 @@ +// If one Debugger's onPop handler causes another Debugger to create a +// Debugger.Frame instance referring to the same frame, that frame still +// gets marked as not live after all the onPop handlers have run. +var g = newGlobal(); +var dbg1 = new Debugger(g); +var dbg2 = new Debugger(g); + +var log; +var frame2; + +dbg1.onEnterFrame = function handleEnter(frame) { + log += '('; + frame.onPop = function handlerPop1(c) { + log += ')'; + frame2 = dbg2.getNewestFrame(); + assertEq(frame2.live, true); + frame2.onPop = function handlePop2(c) { + assertEq("late frame's onPop handler ran", + "late frame's onPop handler should not run"); + }; + }; +}; + +log = ''; +assertEq(g.eval('40 + 2'), 42); +assertEq(log, '()'); +assertEq(frame2.live, false); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-return-error.js b/js/src/jit-test/tests/debug/Frame-onPop-return-error.js new file mode 100644 index 000000000..3aaf5bb0e --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-return-error.js @@ -0,0 +1,59 @@ +// |jit-test| error: TestComplete +// onPop can change a normal return into a termination. + +var g = newGlobal(); +var dbg = new Debugger(g); + +// We use Debugger.Frame.prototype.eval and ignore the outer 'eval' frame so we +// can catch the termination. + +function test(type, provocation) { + // Help people figure out which 'test' call failed. + print("type: " + uneval(type)); + print("provocation: " + uneval(provocation)); + + var log; + dbg.onEnterFrame = function handleFirstFrame(f) { + log += 'f'; + dbg.onDebuggerStatement = function handleDebugger(f) { + log += 'd'; + }; + + dbg.onEnterFrame = function handleSecondFrame(f) { + log += 'e'; + assertEq(f.type, 'eval'); + + dbg.onEnterFrame = function handleThirdFrame(f) { + log += '('; + assertEq(f.type, type); + + dbg.onEnterFrame = function handleExtraFrames(f) { + // This should never be called. + assertEq(false, true); + }; + + f.onPop = function handlePop(c) { + log += ')'; + assertEq(c.return, 'compliment'); + return null; + }; + }; + }; + + assertEq(f.eval(provocation), null); + }; + + log = ''; + // This causes handleFirstFrame to be called. + assertEq(typeof g.eval('eval'), 'function'); + assertEq(log, 'fe(d)'); + + print(); +} + +g.eval('function f() { debugger; return \'compliment\'; }'); +test('call', 'f();'); +test('call', 'new f;'); +test('eval', 'eval(\'debugger; \\\'compliment\\\';\');'); +test('global', 'evaluate(\'debugger; \\\'compliment\\\';\');'); +throw 'TestComplete'; diff --git a/js/src/jit-test/tests/debug/Frame-onPop-return-return.js b/js/src/jit-test/tests/debug/Frame-onPop-return-return.js new file mode 100644 index 000000000..cdcccd194 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-return-return.js @@ -0,0 +1,46 @@ +// |jit-test| error: TestComplete +// onPop can change a normal return into a normal return of a different value. + +var g = newGlobal(); +var dbg = new Debugger(g); + +function test(type, provocation) { + var log; + var wasConstructing; + + // Help people figure out which 'test' call failed. + print("type: " + uneval(type)); + print("provocation: " + uneval(provocation)); + + dbg.onDebuggerStatement = function handleDebuggerStatement(f) { + log += 'd'; + }; + + dbg.onEnterFrame = function handleEnterFrame(f) { + log += '('; + assertEq(f.type, type); + wasConstructing = f.constructing; + f.onPop = function handlePop(c) { + log += ')'; + assertEq(c.return, 'compliment'); + return { return: 'favor' }; + }; + }; + + log = ''; + var result = provocation(); + if (wasConstructing) + assertEq(typeof result, "object"); + else + assertEq(result, 'favor'); + assertEq(log, "(d)"); + + print(); +} + +g.eval("function f() { debugger; return 'compliment'; }"); +test("call", g.f); +test("call", function () { return new g.f; }); +test("eval", function () { return g.eval("debugger; \'compliment\';"); }); +test("global", function () { return g.evaluate("debugger; \'compliment\';"); }); +throw 'TestComplete'; diff --git a/js/src/jit-test/tests/debug/Frame-onPop-return-throw.js b/js/src/jit-test/tests/debug/Frame-onPop-return-throw.js new file mode 100644 index 000000000..f8a845fd7 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-return-throw.js @@ -0,0 +1,41 @@ +// |jit-test| error: TestComplete +// onPop can change a normal return into a throw. + +load(libdir + "asserts.js"); +var g = newGlobal(); +var dbg = new Debugger(g); + +function test(type, provocation) { + var log; + + // Help people figure out which 'test' call failed. + print("type: " + uneval(type)); + print("provocation: " + uneval(provocation)); + + dbg.onDebuggerStatement = function handleDebuggerStatement(f) { + log += 'd'; + }; + + dbg.onEnterFrame = function handleEnterFrame(f) { + log += '('; + assertEq(f.type, type); + f.onPop = function handlePop(c) { + log += ')'; + assertEq(c.return, 'compliment'); + return { throw: 'snow' }; + }; + }; + + log = ''; + assertThrowsValue(provocation, 'snow'); + assertEq(log, "(d)"); + + print(); +} + +g.eval("function f() { debugger; return 'compliment'; }"); +test("call", g.f); +test("call", function () { return new g.f; }); +test("eval", function () { return g.eval("debugger; \'compliment\';"); }); +test("global", function () { return g.evaluate("debugger; \'compliment\';"); }); +throw 'TestComplete'; diff --git a/js/src/jit-test/tests/debug/Frame-onPop-return.js b/js/src/jit-test/tests/debug/Frame-onPop-return.js new file mode 100644 index 000000000..af45c616c --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-return.js @@ -0,0 +1,45 @@ +// |jit-test| error: TestComplete +// onPop fires when frames return normally. + +var g = newGlobal(); +var dbg = new Debugger(g); + +function test(type, provocation) { + var log; + var wasConstructing; + + // Help people figure out which 'test' call failed. + print("type: " + uneval(type)); + print("provocation: " + uneval(provocation)); + + dbg.onDebuggerStatement = function handleDebuggerStatement(f) { + log += 'd'; + }; + + dbg.onEnterFrame = function handleEnterFrame(f) { + log += '('; + assertEq(f.type, type); + wasConstructing = f.constructing; + f.onPop = function handlePop(c) { + log += ')'; + assertEq(c.return, 'compliment'); + }; + }; + + log = ''; + var result = provocation(); + if (wasConstructing) + assertEq(typeof result, "object"); + else + assertEq(result, 'compliment'); + assertEq(log, "(d)"); + + print(); +} + +g.eval("function f() { debugger; return 'compliment'; }"); +test("call", g.f); +test("call", function () { return new g.f; }); +test("eval", function () { return g.eval("debugger; \'compliment\';"); }); +test("global", function () { return g.evaluate("debugger; \'compliment\';"); }); +throw 'TestComplete'; diff --git a/js/src/jit-test/tests/debug/Frame-onPop-star-generators-01.js b/js/src/jit-test/tests/debug/Frame-onPop-star-generators-01.js new file mode 100644 index 000000000..73cf3c8c7 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-star-generators-01.js @@ -0,0 +1,20 @@ +// Returning {throw:} from an onPop handler when yielding works and +// closes the generator-iterator. + +load(libdir + "iteration.js"); + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +dbg.onDebuggerStatement = function handleDebugger(frame) { + frame.onPop = function (c) { + return {throw: "fit"}; + }; +}; +g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }"); +g.eval("var it = g();"); +var rv = gw.executeInGlobal("it.next();"); +assertEq(rv.throw, "fit"); + +dbg.enabled = false; +assertIteratorDone(g.it); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-star-generators-02.js b/js/src/jit-test/tests/debug/Frame-onPop-star-generators-02.js new file mode 100644 index 000000000..de9ad5009 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-star-generators-02.js @@ -0,0 +1,21 @@ +// |jit-test| error: fit + +// Throwing an exception from an onPop handler when yielding terminates the debuggee +// but does not close the generator-iterator. + +load(libdir + 'iteration.js') + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +dbg.onDebuggerStatement = function handleDebugger(frame) { + frame.onPop = function (c) { + throw "fit"; + }; +}; +g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }"); +g.eval("var it = g();"); +assertEq(gw.executeInGlobal("it.next();"), null); + +dbg.enabled = false; +assertIteratorNext(g.it, 1); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-star-generators-03.js b/js/src/jit-test/tests/debug/Frame-onPop-star-generators-03.js new file mode 100644 index 000000000..5e701a3ab --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-star-generators-03.js @@ -0,0 +1,42 @@ +// Each resumption of an ES6 generator gets a fresh frame, whose onPop +// handler fires the next time the generator yields. This is not the +// behavior the spec requests, but it's what we do for the moment, and +// it's good to check that at least we don't crash. + +load(libdir + 'iteration.js'); + +var g = newGlobal(); +var dbg = new Debugger(g); +var log; + +var debuggerFrames = []; +var poppedFrames = []; +dbg.onDebuggerStatement = function handleDebugger(frame) { + log += 'd'; + assertEq(frame.type, "call"); + + assertEq(debuggerFrames.indexOf(frame), -1); + assertEq(poppedFrames.indexOf(frame), -1); + debuggerFrames.push(frame); + + if (frame.eval('i').return % 3 == 0) { + frame.onPop = function handlePop(c) { + log += ')' + c.return.value; + assertEq(debuggerFrames.indexOf(this) != -1, true); + assertEq(poppedFrames.indexOf(this), -1); + poppedFrames.push(this); + }; + } +}; + +g.eval("function* g() { for (var i = 0; i < 10; i++) { debugger; yield i; } }"); +log =''; +g.eval("var t = 0, iter = g();"); +for (var j = 0; j < 10; j++) + g.eval("t += iter.next().value;"); +assertIteratorResult(g.eval("iter.next()"), undefined, true); +assertEq(g.eval("t"), 45); + +// FIXME: Should equal this, but see bug 917809. +// assertEq(log, "d)0ddd)3ddd)6ddd)9"); +assertEq(log, "d)undefinedddd)undefinedddd)undefinedddd)undefined"); diff --git a/js/src/jit-test/tests/debug/Frame-onPop-throw-error.js b/js/src/jit-test/tests/debug/Frame-onPop-throw-error.js new file mode 100644 index 000000000..e0c7b78df --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-throw-error.js @@ -0,0 +1,59 @@ +// |jit-test| error: TestComplete +// onPop can change a throw into a termination. + +var g = newGlobal(); +var dbg = new Debugger(g); + +// We use Debugger.Frame.prototype.eval and ignore the outer 'eval' frame so we +// can catch the termination. + +function test(type, provocation) { + // Help people figure out which 'test' call failed. + print("type: " + uneval(type)); + print("provocation: " + uneval(provocation)); + + var log; + dbg.onEnterFrame = function handleFirstFrame(f) { + log += 'f'; + dbg.onDebuggerStatement = function handleDebugger(f) { + log += 'd'; + }; + + dbg.onEnterFrame = function handleSecondFrame(f) { + log += 'e'; + assertEq(f.type, 'eval'); + + dbg.onEnterFrame = function handleThirdFrame(f) { + log += '('; + assertEq(f.type, type); + + dbg.onEnterFrame = function handleExtraFrames(f) { + // This should never be called. + assertEq(false, true); + }; + + f.onPop = function handlePop(c) { + log += ')'; + assertEq(c.throw, 'mud'); + return null; + }; + }; + }; + + assertEq(f.eval(provocation), null); + }; + + log = ''; + // This causes handleFirstFrame to be called. + assertEq(typeof g.eval('eval'), 'function'); + assertEq(log, 'fe(d)'); + + print(); +} + +g.eval('function f() { debugger; throw \'mud\'; }'); +test('call', 'f();'); +test('call', 'new f;'); +test('eval', 'eval(\'debugger; throw \\\'mud\\\';\');'); +test('global', 'evaluate(\'debugger; throw \\\'mud\\\';\');'); +throw 'TestComplete'; diff --git a/js/src/jit-test/tests/debug/Frame-onPop-throw-return.js b/js/src/jit-test/tests/debug/Frame-onPop-throw-return.js new file mode 100644 index 000000000..bd35b6e16 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-throw-return.js @@ -0,0 +1,46 @@ +// |jit-test| error: TestComplete +// onPop can change a throw into a normal return. + +var g = newGlobal(); +var dbg = new Debugger(g); + +function test(type, provocation) { + var log; + var wasConstructing; + + // Help people figure out which 'test' call failed. + print("type: " + uneval(type)); + print("provocation: " + uneval(provocation)); + + dbg.onDebuggerStatement = function handleDebuggerStatement(f) { + log += 'd'; + }; + + dbg.onEnterFrame = function handleEnterFrame(f) { + log += '('; + assertEq(f.type, type); + wasConstructing = f.constructing; + f.onPop = function handlePop(c) { + log += ')'; + assertEq(c.throw, 'mud'); + return { return: 'favor' }; + }; + }; + + log = ''; + var result = provocation(); + if (wasConstructing) + assertEq(typeof result, "object"); + else + assertEq(result, 'favor'); + assertEq(log, "(d)"); + + print(); +} + +g.eval("function f() { debugger; throw 'mud'; }"); +test("call", g.f); +test("call", function () { return new g.f; }); +test("eval", function () { return g.eval("debugger; throw \'mud\';"); }); +test("global", function () { return g.evaluate("debugger; throw \'mud\';"); }); +throw 'TestComplete'; diff --git a/js/src/jit-test/tests/debug/Frame-onPop-throw-throw.js b/js/src/jit-test/tests/debug/Frame-onPop-throw-throw.js new file mode 100644 index 000000000..1c27f0f34 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-throw-throw.js @@ -0,0 +1,41 @@ +// |jit-test| error: TestComplete +// onPop can change a throw into a throw of a different value. + +load(libdir + "asserts.js"); +var g = newGlobal(); +var dbg = new Debugger(g); + +function test(type, provocation) { + var log; + + // Help people figure out which 'test' call failed. + print("type: " + uneval(type)); + print("provocation: " + uneval(provocation)); + + dbg.onDebuggerStatement = function handleDebuggerStatement(f) { + log += 'd'; + }; + + dbg.onEnterFrame = function handleEnterFrame(f) { + log += '('; + assertEq(f.type, type); + f.onPop = function handlePop(c) { + log += ')'; + assertEq(c.throw, 'mud'); + return { throw: 'snow' }; + }; + }; + + log = ''; + assertThrowsValue(provocation, 'snow'); + assertEq(log, "(d)"); + + print(); +} + +g.eval("function f() { debugger; throw 'mud'; }"); +test("call", g.f); +test("call", function () { return new g.f; }); +test("eval", function () { return g.eval("debugger; throw \'mud\';"); }); +test("global", function () { return g.evaluate("debugger; throw \'mud\';"); }); +throw 'TestComplete'; diff --git a/js/src/jit-test/tests/debug/Frame-onPop-throw.js b/js/src/jit-test/tests/debug/Frame-onPop-throw.js new file mode 100644 index 000000000..e50b43240 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onPop-throw.js @@ -0,0 +1,40 @@ +// |jit-test| error: TestComplete +// onPop fires when frames throw an exception. + +load(libdir + "asserts.js"); +var g = newGlobal(); +var dbg = new Debugger(g); + +function test(type, provocation) { + var log; + + // Help people figure out which 'test' call failed. + print("type: " + uneval(type)); + print("provocation: " + uneval(provocation)); + + dbg.onDebuggerStatement = function handleDebuggerStatement(f) { + log += 'd'; + }; + + dbg.onEnterFrame = function handleEnterFrame(f) { + log += '('; + assertEq(f.type, type); + f.onPop = function handlePop(c) { + log += ')'; + assertEq(c.throw, 'mud'); + }; + }; + + log = ''; + assertThrowsValue(provocation, 'mud'); + assertEq(log, "(d)"); + + print(); +} + +g.eval("function f() { debugger; throw 'mud'; }"); +test("call", g.f); +test("call", function () { return new g.f; }); +test("eval", function () { return g.eval("debugger; throw \'mud\';"); }); +test("global", function () { return g.evaluate("debugger; throw \'mud\';"); }); +throw 'TestComplete'; diff --git a/js/src/jit-test/tests/debug/Frame-onStep-01.js b/js/src/jit-test/tests/debug/Frame-onStep-01.js new file mode 100644 index 000000000..28887f747 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-01.js @@ -0,0 +1,24 @@ +// Simple Debugger.Frame.prototype.onStep test. +// Test that onStep fires often enough to see all four values of a. + +var g = newGlobal(); +g.a = 0; +g.eval("function f() {\n" + + " a += 2;\n" + + " a += 2;\n" + + " a += 2;\n" + + " return a;\n" + + "}\n"); + +var dbg = Debugger(g); +var seen = [0, 0, 0, 0, 0, 0, 0]; +dbg.onEnterFrame = function (frame) { + frame.onStep = function () { + assertEq(arguments.length, 0); + assertEq(this, frame); + seen[g.a] = 1; + }; +} + +g.f(); +assertEq(seen.join(""), "1010101"); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-02.js b/js/src/jit-test/tests/debug/Frame-onStep-02.js new file mode 100644 index 000000000..6e02449bc --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-02.js @@ -0,0 +1,27 @@ +// Setting frame.onStep to undefined turns off single-stepping. + +var g = newGlobal(); +g.a = 0; +g.eval("function f() {\n" + + " a++;\n" + + " a++;\n" + + " a++;\n" + + " a++;\n" + + " return a;\n" + + "}\n"); + +var dbg = Debugger(g); +var seen = [0, 0, 0, 0, 0]; +dbg.onEnterFrame = function (frame) { + seen[g.a] = 1; + frame.onStep = function () { + seen[g.a] = 1; + if (g.a === 2) { + frame.onStep = undefined; + assertEq(frame.onStep, undefined); + } + }; +} + +g.f(); +assertEq(seen.join(","), "1,1,1,0,0"); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-03.js b/js/src/jit-test/tests/debug/Frame-onStep-03.js new file mode 100644 index 000000000..14e535156 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-03.js @@ -0,0 +1,28 @@ +// Setting onStep does not affect later calls to the same function. +// (onStep is per-frame, not per-function.) + +var g = newGlobal(); +g.a = 1; +g.eval("function f(a) {\n" + + " var x = 2 * a;\n" + + " return x * x;\n" + + "}\n"); + +var dbg = Debugger(g); +var log = ''; +dbg.onEnterFrame = function (frame) { + log += '+'; + frame.onStep = function () { + if (log.charAt(log.length - 1) != 's') + log += 's'; + }; +}; + +g.f(1); +log += '|'; +g.f(2); +log += '|'; +dbg.onEnterFrame = undefined; +g.f(3); + +assertEq(log, '+s|+s|'); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-04.js b/js/src/jit-test/tests/debug/Frame-onStep-04.js new file mode 100644 index 000000000..d144a3988 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-04.js @@ -0,0 +1,34 @@ +// When a recursive function has many frames on the stack, onStep may be set or +// not independently on each frame. + +var g = newGlobal(); +g.eval("function f(x) {\n" + + " if (x > 0)\n" + + " f(x - 1);\n" + + " else\n" + + " debugger;\n" + + " return x;\n" + + "}"); + +var dbg = Debugger(g); +var seen = [0, 0, 0, 0, 0, 0, 0, 0]; +function step() { + seen[this.arguments[0]] = 1; +} +dbg.onEnterFrame = function (frame) { + // Turn on stepping for even-numbered frames. + var x = frame.arguments[0]; + if (x % 2 === 0) + frame.onStep = step; +}; +dbg.onDebuggerStatement = function (frame) { + // This is called with 8 call frames on the stack, 7 down to 0. + // At this point we should have seen all the even-numbered frames. + assertEq(seen.join(""), "10101010"); + + // Now reset seen to see which frames fire onStep on the way out. + seen = [0, 0, 0, 0, 0, 0, 0, 0]; +}; + +g.f(7); +assertEq(seen.join(""), "10101010"); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-05.js b/js/src/jit-test/tests/debug/Frame-onStep-05.js new file mode 100644 index 000000000..5fcdba945 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-05.js @@ -0,0 +1,14 @@ +// Upon returning to a frame with an onStep hook, the hook is called before the +// next line. + +var g = newGlobal(); +g.log = ''; +g.eval("function f() { debugger; }"); + +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + frame.older.onStep = function () { g.log += 's'; }; +}; +g.eval("f();\n" + + "log += 'x';\n"); +assertEq(g.log.charAt(0), 's'); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-06.js b/js/src/jit-test/tests/debug/Frame-onStep-06.js new file mode 100644 index 000000000..c447d83d7 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-06.js @@ -0,0 +1,66 @@ +// After returning from an implicit toString call, the calling frame's onStep +// hook fires. + +var g = newGlobal(); +g.eval("var originalX = {toString: function () { debugger; log += 'x'; return 1; }};\n"); + +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + g.log += 'd'; + frame.older.onStep = function () { + if (!g.log.match(/[sy]$/)) + g.log += 's'; + }; +}; + +// expr is an expression that will trigger an implicit toString call. +function check(expr) { + g.log = ''; + g.x = g.originalX; + g.eval(expr + ";\n" + + "log += 'y';\n"); + assertEq(g.log, 'dxsy'); +} + +check("'' + x"); +check("0 + x"); +check("0 - x"); +check("0 * x"); +check("0 / x"); +check("0 % x"); +check("+x"); +check("x in {}"); +check("x++"); +check("++x"); +check("x--"); +check("--x"); +check("x < 0"); +check("x > 0"); +check("x >= 0"); +check("x <= 0"); +check("x == 0"); +check("x != 0"); +check("x & 1"); +check("x | 1"); +check("x ^ 1"); +check("~x"); +check("x << 1"); +check("x >> 1"); +check("x >>> 1"); + +g.eval("function lastStep() { throw StopIteration; }"); +g.eval("function emptyIterator() { debugger; log += 'x'; return { next: lastStep }; }"); +g.eval("var customEmptyIterator = { __iterator__: emptyIterator };"); +g.log = ''; +g.eval("for (i in customEmptyIterator);\n" + + "log += 'y';\n"); +assertEq(g.log, 'dxsy'); + +g.eval("var getter = { get x() { debugger; return log += 'x'; } }"); +check("getter.x"); + +g.eval("var setter = { set x(v) { debugger; return log += 'x'; } }"); +check("setter.x = 1"); + +g.eval("Object.defineProperty(this, 'thisgetter', { get: function() { debugger; log += 'x'; }});"); +check("thisgetter"); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-07.js b/js/src/jit-test/tests/debug/Frame-onStep-07.js new file mode 100644 index 000000000..6f6e1413f --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-07.js @@ -0,0 +1,23 @@ +// The tracejit does not interfere with frame.onStep. +// +// The function f() writes 'L' to the log in a loop. If we enable stepping and +// write an 's' each time frame.onStep is called, any two Ls should have at +// least one 's' between them. + +var g = newGlobal(); +g.N = 11; +g.log = ''; +g.eval("function f() {\n" + + " for (var i = 0; i <= N; i++)\n" + + " log += 'L';\n" + + "}\n"); +g.f(); +assertEq(/LL/.exec(g.log) !== null, true); + +var dbg = Debugger(g); +dbg.onEnterFrame = function (frame) { + frame.onStep = function () { g.log += 's'; }; +}; +g.log = ''; +g.f(); +assertEq(/LL/.exec(g.log), null); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-08.js b/js/src/jit-test/tests/debug/Frame-onStep-08.js new file mode 100644 index 000000000..106f2bbf9 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-08.js @@ -0,0 +1,29 @@ +// frame.onStep can coexist with breakpoints. + +var g = newGlobal(); +var dbg = Debugger(g); +var log = ''; +dbg.onEnterFrame = function (frame) { + var handler = {hit: function () { log += 'B'; }}; + var lines = frame.script.getAllOffsets(); + for (var line in lines) { + line = Number(line); + var offs = lines[line]; + for (var i = 0; i < offs.length; i++) + frame.script.setBreakpoint(offs[i], handler); + } + + frame.onStep = function () { log += 's'; }; +}; + +g.eval("one = 1;\n" + + "two = 2;\n" + + "three = 3;\n" + + "four = 4;\n"); +assertEq(g.four, 4); + +// Breakpoints hit on all four lines. +assertEq(log.replace(/[^B]/g, ''), 'BBBB'); + +// onStep was called between each pair of breakpoints. +assertEq(/BB/.exec(log), null); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-09.js b/js/src/jit-test/tests/debug/Frame-onStep-09.js new file mode 100644 index 000000000..58333719e --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-09.js @@ -0,0 +1,24 @@ +// After an implicit toString call throws an exception, the calling frame's +// onStep hook fires. + +var g = newGlobal(); +g.eval("var x = {toString: function () { debugger; log += 'x'; throw 'mud'; }};"); + +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + g.log += 'd'; + frame.older.onStep = function () { + if (!g.log.match(/[sy]$/)) + g.log += 's'; + }; +}; + +g.log = ''; +g.eval("try { x + ''; } catch (x) { }\n" + + "log += 'y';\n"); +assertEq(g.log, "dxsy"); + +g.log = ''; +g.eval("try { '' + x; } catch (x) { }\n" + + "log += 'y';\n"); +assertEq(g.log, "dxsy"); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-10.js b/js/src/jit-test/tests/debug/Frame-onStep-10.js new file mode 100644 index 000000000..dad19deff --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-10.js @@ -0,0 +1,28 @@ +// Throwing and catching an error in an onStep handler shouldn't interfere +// with throwing and catching in the debuggee. + +var g = newGlobal(); +g.eval("function f() { debugger; throw 'mud'; }"); + +var dbg = Debugger(g); +var stepped = false; +dbg.onDebuggerStatement = function (frame) { + frame.older.onStep = function () { + stepped = true; + try { + throw 'snow'; + } catch (x) { + assertEq(x, 'snow'); + } + }; +}; + +stepped = false; +g.eval("var caught;\n" + + "try {\n" + + " f();\n" + + "} catch (x) {\n" + + " caught = x;\n" + + "}\n"); +assertEq(stepped, true); +assertEq(g.caught, 'mud'); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-11.js b/js/src/jit-test/tests/debug/Frame-onStep-11.js new file mode 100644 index 000000000..dae366165 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-11.js @@ -0,0 +1,36 @@ +// Stepping out of a finally should not appear to +// step backward to some earlier statement. + +var g = newGlobal(); +g.eval(`function f() { + debugger; // +0 + var x = 0; // +1 + try { // +2 + x = 1; // +3 + throw 'something'; // +4 + } catch (e) { // +5 + x = 2; // +6 + } finally { // +7 + x = 3; // +8 + } // +9 + x = 4; // +10 + }`); // +11 + +var dbg = Debugger(g); + +let foundLines = ''; + +dbg.onDebuggerStatement = function(frame) { + let debugLine = frame.script.getOffsetLocation(frame.offset).lineNumber; + frame.onStep = function() { + // Only record a stop when the offset is an entry point. + let foundLine = this.script.getOffsetLocation(this.offset).lineNumber; + if (foundLine != debugLine && this.script.getLineOffsets(foundLine).indexOf(this.offset) >= 0) { + foundLines += "," + (foundLine - debugLine); + } + }; +}; + +g.f(); + +assertEq(foundLines, ",1,2,3,4,5,6,7,8,10,11"); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-12.js b/js/src/jit-test/tests/debug/Frame-onStep-12.js new file mode 100644 index 000000000..4db38b313 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-12.js @@ -0,0 +1,129 @@ +// Because our script source notes record only those bytecode offsets +// at which source positions change, the default behavior in the +// absence of a source note is to attribute a bytecode instruction to +// the same source location as the preceding instruction. When control +// flows from the preceding bytecode to the one we're emitting, that's +// usually plausible. But successors in the bytecode stream are not +// necessarily successors in the control flow graph. If the preceding +// bytecode was a back edge of a loop, or the jump at the end of a +// 'then' clause, its source position can be completely unrelated to +// that of its successor. + +// We try to avoid showing such nonsense offsets to the user by +// requiring breakpoints and single-stepping to stop only at a line's +// entry points, as reported by Debugger.Script.prototype.getLineOffsets; +// and then ensuring that those entry points are all offsets mentioned +// explicitly in the source notes, and hence deliberately attributed +// to the given bytecode. + +// This bit of JavaScript compiles to bytecode ending in a branch +// instruction whose source position is the body of an unreachable +// loop. The first instruction of the bytecode we emit following it +// will inherit this nonsense position, if we have not explicitly +// emitted a source note for said instruction. + +// This test steps across such code and verifies that control never +// appears to enter the unreachable loop. + +var bitOfCode = `debugger; // +0 + if(false) { // +1 + for(var b=0; b<0; b++) { // +2 + c = 2 // +3 + } // +4 + }`; // +5 + +var g = newGlobal(); +var dbg = Debugger(g); + +g.eval("function nothing() { }\n"); + +var log = ''; +dbg.onDebuggerStatement = function(frame) { + let debugLine = frame.script.getOffsetLocation(frame.offset).lineNumber; + frame.onStep = function() { + let foundLine = this.script.getOffsetLocation(this.offset).lineNumber; + if (this.script.getLineOffsets(foundLine).indexOf(this.offset) >= 0) { + log += (foundLine - debugLine).toString(16); + } + }; +}; + +function testOne(name, body, expected) { + print(name); + log = ''; + g.eval(`function ${name} () { ${body} }`); + g.eval(`${name}();`); + assertEq(log, expected); +} + + + +// Test the instructions at the end of a "try". +testOne("testTryFinally", + `try { + ${bitOfCode} + } finally { // +6 + } // +7 + nothing(); // +8 + `, "1689"); + +// The same but without a finally clause. +testOne("testTryCatch", + `try { + ${bitOfCode} + } catch (e) { // +6 + } // +7 + nothing(); // +8 + `, "189"); + +// Test the instructions at the end of a "catch". +testOne("testCatchFinally", + `try { + throw new TypeError(); + } catch (e) { + ${bitOfCode} + } finally { // +6 + } // +7 + nothing(); // +8 + `, "1689"); + +// The same but without a finally clause. This relies on a +// SpiderMonkey extension, because otherwise there's no way to see +// extra instructions at the end of a catch. +testOne("testCatch", + `try { + throw new TypeError(); + } catch (e if e instanceof TypeError) { + ${bitOfCode} + } catch (e) { // +6 + } // +7 + nothing(); // +8 + `, "189"); + +// Test the instruction at the end of a "finally" clause. +testOne("testFinally", + `try { + } finally { + ${bitOfCode} + } // +6 + nothing(); // +7 + `, "178"); + +// Test the instruction at the end of a "then" clause. +testOne("testThen", + `if (1 === 1) { + ${bitOfCode} + } else { // +6 + } // +7 + nothing(); // +8 + `, "189"); + +// Test the instructions leaving a switch block. +testOne("testSwitch", + `var x = 5; + switch (x) { + case 5: + ${bitOfCode} + } // +6 + nothing(); // +7 + `, "178"); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-13.js b/js/src/jit-test/tests/debug/Frame-onStep-13.js new file mode 100644 index 000000000..cc2c18c6d --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-13.js @@ -0,0 +1,29 @@ +// Stepping over a not-taken "if" that is at the end of the function +// should move to the end of the function, not somewhere in the body +// of the "if". + +var g = newGlobal(); +g.eval(`function f() { // 1 + var a,c; // 2 + debugger; // 3 + if(false) { // 4 + for(var b=0; b<0; b++) { // 5 + c = 2; // 6 + } // 7 + } // 8 +} // 9 +`); + +var dbg = Debugger(g); +var badStep = false; + +dbg.onDebuggerStatement = function(frame) { + let debugLine = frame.script.getOffsetLocation(frame.offset).lineNumber; + assertEq(debugLine, 3); + frame.onStep = function() { + let foundLine = this.script.getOffsetLocation(this.offset).lineNumber; + assertEq(foundLine <= 4 || foundLine >= 8, true); + }; +}; + +g.eval("f();\n"); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-14.js b/js/src/jit-test/tests/debug/Frame-onStep-14.js new file mode 100644 index 000000000..2e77d5647 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-14.js @@ -0,0 +1,46 @@ +// Test how stepping interacts with switch statements. + +var g = newGlobal(); + +g.eval('function bob() { return "bob"; }'); + +// Stepping into a sparse switch should not stop on literal cases. +evaluate(`function lit(x) { // 1 + debugger; // 2 + switch(x) { // 3 + case "nope": // 4 + break; // 5 + case "bob": // 6 + break; // 7 + } // 8 +}`, {lineNumber: 1, global: g}); + +// Stepping into a sparse switch should stop on non-literal cases. +evaluate(`function nonlit(x) { // 1 + debugger; // 2 + switch(x) { // 3 + case bob(): // 4 + break; // 5 + } // 6 +}`, {lineNumber: 1, global: g}); + +var dbg = Debugger(g); +var badStep = false; + +function test(s, okLine) { + dbg.onDebuggerStatement = function(frame) { + frame.onStep = function() { + let thisLine = this.script.getOffsetLocation(this.offset).lineNumber; + // The stop at line 3 is the switch. + if (thisLine > 3) { + assertEq(thisLine, okLine) + frame.onStep = undefined; + } + }; + }; + g.eval(s); +} + +test("lit('bob');", 7); + +test("nonlit('bob');", 4); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-15.js b/js/src/jit-test/tests/debug/Frame-onStep-15.js new file mode 100644 index 000000000..539ff5f05 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-15.js @@ -0,0 +1,43 @@ +// Test how stepping interacts with for(;;) statements. + +let g = newGlobal(); + +// We want a for(;;) loop whose body is evaluated at least once, to +// see whether the loop head is hit a second time. +g.eval(`function f() { + let x = 0; + debugger; // +0 + for(;;) { // +1 + if (x++ == 1) break; // +2 + } // +3 + debugger; // +4 +}`); + +let dbg = Debugger(g); + +function test(s, expected) { + let result = ''; + + dbg.onDebuggerStatement = function(frame) { + // On the second debugger statement, we're done. + dbg.onDebuggerStatement = function(frame) { + frame.onStep = undefined; + }; + + let debugLine = frame.script.getOffsetLocation(frame.offset).lineNumber; + frame.onStep = function() { + // Only examine stops at entry points for the line. + let lineNo = this.script.getOffsetLocation(this.offset).lineNumber; + if (this.script.getLineOffsets(lineNo).indexOf(this.offset) < 0) { + return undefined; + } + + let delta = this.script.getOffsetLocation(this.offset).lineNumber - debugLine; + result += delta; + }; + }; + g.eval(s); + assertEq(result, expected); +} + +test('f()', '2124'); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-16.js b/js/src/jit-test/tests/debug/Frame-onStep-16.js new file mode 100644 index 000000000..b011da428 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-16.js @@ -0,0 +1,34 @@ +// Stepping through a function with a return statement should pause on +// the closing brace of the function. + +var g = newGlobal(); +var dbg = Debugger(g); +var log; + +function test(fnStr) { + log = ''; + g.eval(fnStr); + + dbg.onDebuggerStatement = function(frame) { + frame.onStep = function() { + let {lineNumber, isEntryPoint} = frame.script.getOffsetLocation(frame.offset); + if (isEntryPoint) { + log += lineNumber + ' '; + } + }; + }; + + g.eval("f(23);"); +} + +test("function f(x) {\n" + // 1 + " debugger;\n" + // 2 + " return 23 + x;\n" + // 3 + "}\n"); // 4 +assertEq(log, '3 4 '); + +test("function f(x) {\n" + // 1 + " debugger;\n" + // 2 + " return;\n" + // 3 + "}\n"); // 4 +assertEq(log, '3 4 '); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-iterators.js b/js/src/jit-test/tests/debug/Frame-onStep-iterators.js new file mode 100644 index 000000000..1b9fa6585 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-iterators.js @@ -0,0 +1,20 @@ +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var log; +var a = []; + +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + frame.onStep = function () { + // This handler must not wipe out the debuggee's value in JSContext::iterValue. + log += 's'; + // This will use JSContext::iterValue in the debugger. + for (let i of a) + log += 'i'; + }; +}; + +log = ''; +g.eval("debugger; for (let i of [1,2,3]) print(i);"); +assertEq(!!log.match(/^ds*$/), true); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-lines-01.js b/js/src/jit-test/tests/debug/Frame-onStep-lines-01.js new file mode 100644 index 000000000..0c486971c --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-lines-01.js @@ -0,0 +1,78 @@ +// Test that a frame's onStep handler gets called at least once on each line of a function. + +var g = newGlobal(); +var dbg = new Debugger(g); + +// When we hit a 'debugger' statement, set offsets to the frame's script's +// table of line offsets --- a sparse array indexed by line number. Begin +// single-stepping the current frame; for each source line we hit, delete +// the line's entry in offsets. Thus, at the end, offsets is an array with +// an element for each line we did not reach. +var doSingleStep = true; +var offsets; +dbg.onDebuggerStatement = function (frame) { + var script = frame.script; + offsets = script.getAllOffsets(); + print("debugger line: " + script.getOffsetLocation(frame.offset).lineNumber); + print("original lines: " + uneval(Object.keys(offsets))); + if (doSingleStep) { + frame.onStep = function onStepHandler() { + var line = script.getOffsetLocation(this.offset).lineNumber; + delete offsets[line]; + }; + } +}; + +g.eval( + 'function t(a, b, c) { \n' + + ' debugger; \n' + + ' var x = a; \n' + + ' x += b; \n' + + ' if (x < 10) \n' + + ' x -= c; \n' + + ' return x; \n' + + '} \n' + ); + +// This should stop at every line but the first of the function. +g.eval('t(1,2,3)'); +assertEq(Object.keys(offsets).length, 1); + +// This should stop at every line but the first of the function, and the +// body of the 'if'. +g.eval('t(10,20,30)'); +assertEq(Object.keys(offsets).length, 2); + +// This shouldn't stop at all. It's the frame that's in single-step mode, +// not the script, so the prior execution of t in single-step mode should +// have no effect on this one. +doSingleStep = false; +g.eval('t(0, 0, 0)'); +assertEq(Object.keys(offsets).length, 7); +doSingleStep = true; + +// Single-step in an eval frame. This should reach every line but the +// first. +g.eval( + 'debugger; \n' + + 'var a=1, b=2, c=3; \n' + + 'var x = a; \n' + + 'x += b; \n' + + 'if (x < 10) \n' + + ' x -= c; \n' + ); +print("final lines: " + uneval(Object.keys(offsets))); +assertEq(Object.keys(offsets).length, 1); + +// Single-step in a global code frame. This should reach every line but the +// first. +g.evaluate( + 'debugger; \n' + + 'var a=1, b=2, c=3; \n' + + 'var x = a; \n' + + 'x += b; \n' + + 'if (x < 10) \n' + + ' x -= c; \n' + ); +print("final lines: " + uneval(Object.keys(offsets))); +assertEq(Object.keys(offsets).length, 1); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-resumption-01.js b/js/src/jit-test/tests/debug/Frame-onStep-resumption-01.js new file mode 100644 index 000000000..45a474724 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-resumption-01.js @@ -0,0 +1,14 @@ +// If frame.onStep returns {return:val}, the frame returns. + +var g = newGlobal(); +g.eval("function f(x) {\n" + + " var a = x * x;\n" + + " return a;\n" + + "}\n"); + +var dbg = Debugger(g); +dbg.onEnterFrame = function (frame) { + frame.onStep = function () { return {return: "pass"}; }; +}; + +assertEq(g.f(4), "pass"); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-resumption-02.js b/js/src/jit-test/tests/debug/Frame-onStep-resumption-02.js new file mode 100644 index 000000000..98b3a4011 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-resumption-02.js @@ -0,0 +1,17 @@ +// If frame.onStep returns {throw:}, an exception is thrown in the debuggee. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +g.eval("function h() { debugger; }\n" + + "function f() {\n" + + " h();\n" + + " return 'fail';\n" + + "}\n"); + +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + frame.older.onStep = function () { return {throw: "pass"}; }; +}; + +assertThrowsValue(g.f, "pass"); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-resumption-03.js b/js/src/jit-test/tests/debug/Frame-onStep-resumption-03.js new file mode 100644 index 000000000..d2ee163ee --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-resumption-03.js @@ -0,0 +1,19 @@ +// If frame.onStep returns null, the debuggee terminates. + +var g = newGlobal(); +g.eval("function h() { debugger; }"); + +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + hits++; + if (hits == 1) { + var rv = frame.eval("h();\n" + + "throw 'fail';\n"); + assertEq(rv, null); + } else { + frame.older.onStep = function () { return null; }; + } +}; +g.h(); +assertEq(hits, 2); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-resumption-04.js b/js/src/jit-test/tests/debug/Frame-onStep-resumption-04.js new file mode 100644 index 000000000..07f2d5c0e --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-resumption-04.js @@ -0,0 +1,31 @@ +// If frame.onStep returns null, debuggee catch and finally blocks are skipped. + +var g = newGlobal(); +g.eval("function h() { debugger; }"); + +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + hits++; + if (hits == 1) { + var rv = frame.eval("try {\n" + + " h();\n" + + " throw 'fail';\n" + + "} catch (exc) {\n" + + " caught = exc;\n" + + "} finally {\n" + + " finallyHit = true;\n" + + "}\n"); + assertEq(rv, null); + } else { + frame.older.onStep = function () { + this.onStep = undefined; + return null; + }; + } +}; + +g.h(); +assertEq(hits, 2); +assertEq("caught" in g, false); +assertEq("finallyHit" in g, false); diff --git a/js/src/jit-test/tests/debug/Frame-onStep-resumption-05.js b/js/src/jit-test/tests/debug/Frame-onStep-resumption-05.js new file mode 100644 index 000000000..aae12d224 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-onStep-resumption-05.js @@ -0,0 +1,55 @@ +// Test that invoking the interrupt callback counts as a step. + +function testResumptionVal(resumptionVal, turnOffDebugMode) { + var g = newGlobal(); + var dbg = new Debugger; + g.log = ""; + g.resumptionVal = resumptionVal; + + setInterruptCallback(function () { + g.log += "i"; + dbg.addDebuggee(g); + var frame = dbg.getNewestFrame(); + frame.onStep = function () { + g.log += "s"; + frame.onStep = undefined; + + if (turnOffDebugMode) + dbg.removeDebuggee(g); + + return resumptionVal; + }; + return true; + }); + + try { + return g.eval("(" + function f() { + log += "f"; + invokeInterruptCallback(function (interruptRv) { + log += "r"; + assertEq(interruptRv, resumptionVal == undefined); + }); + log += "a"; + return 42; + } + ")();"); + } finally { + assertEq(g.log, resumptionVal == undefined ? "fisra" : "fisr"); + } +} + +assertEq(testResumptionVal(undefined), 42); +assertEq(testResumptionVal({ return: "not 42" }), "not 42"); +try { + testResumptionVal({ throw: "thrown 42" }); +} catch (e) { + assertEq(e, "thrown 42"); +} + +assertEq(testResumptionVal(undefined, true), 42); +assertEq(testResumptionVal({ return: "not 42" }, true), "not 42"); + +try { + testResumptionVal({ throw: "thrown 42" }, true); +} catch (e) { + assertEq(e, "thrown 42"); +} diff --git a/js/src/jit-test/tests/debug/Frame-script-01.js b/js/src/jit-test/tests/debug/Frame-script-01.js new file mode 100644 index 000000000..fbf1d0bd8 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-script-01.js @@ -0,0 +1,25 @@ +// Frame.prototype.script for eval frames. + +var g = newGlobal(); +var dbg = new Debugger(g); + +// Apply |f| to each frame that is |skip| frames up from each frame that +// executes a 'debugger' statement when evaluating |code| in the global g. +function ApplyToFrameScript(code, skip, f) { + dbg.onDebuggerStatement = function (frame) { + while (skip-- > 0) + frame = frame.older; + assertEq(frame.type, "eval"); + f(frame.script); + }; + g.eval(code); +} + +ApplyToFrameScript('debugger;', 0, + function (script) { + assertEq(script instanceof Debugger.Script, true); + }); +ApplyToFrameScript("(function () { eval('debugger;'); })();", 0, + function (script) { + assertEq(script instanceof Debugger.Script, true); + }); diff --git a/js/src/jit-test/tests/debug/Frame-script-02.js b/js/src/jit-test/tests/debug/Frame-script-02.js new file mode 100644 index 000000000..1ca139bb7 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-script-02.js @@ -0,0 +1,27 @@ +// Frame.prototype.script for call frames. + +var g = newGlobal(); +var dbg = new Debugger(g); + +// Apply |f| to each frame that is |skip| frames up from each frame that +// executes a 'debugger' statement when evaluating |code| in the global g. +function ApplyToFrameScript(code, skip, f) { + dbg.onDebuggerStatement = function (frame) { + while (skip-- > 0) + frame = frame.older; + assertEq(frame.type, "call"); + f(frame.script); + }; + g.eval(code); +} + +ApplyToFrameScript('(function () { debugger; })();', 0, + function (script) { + assertEq(script instanceof Debugger.Script, true); + }); + +// This would be nice, once we can get host call frames: +// ApplyToFrameScript("(function () { debugger; }).call(null);", 1, +// function (script) { +// assertEq(script, null); +// }); diff --git a/js/src/jit-test/tests/debug/Frame-script-03.js b/js/src/jit-test/tests/debug/Frame-script-03.js new file mode 100644 index 000000000..37d410c11 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-script-03.js @@ -0,0 +1,8 @@ +// frame.script can create a Debugger.Script for a JS_Evaluate* script. + +var g = newGlobal(); +var dbg = Debugger(g); +var s; +dbg.onDebuggerStatement = function (frame) { s = frame.script; }; +g.evaluate("debugger;"); +assertEq(s instanceof Debugger.Script, true); diff --git a/js/src/jit-test/tests/debug/Frame-script-environment-nondebuggee.js b/js/src/jit-test/tests/debug/Frame-script-environment-nondebuggee.js new file mode 100644 index 000000000..7965ea555 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-script-environment-nondebuggee.js @@ -0,0 +1,32 @@ +// The script and environment of a non-debuggee frame are inaccessible. + +load(libdir + 'asserts.js'); + +var g = newGlobal(); +var dbg = new Debugger; + +var log; +dbg.onDebuggerStatement = function (frame) { + log += frame.type; + // Initially, 'frame' is a debuggee frame, and we should be able to see its script and environment. + assertEq(frame.script instanceof Debugger.Script, true); + assertEq(frame.environment instanceof Debugger.Environment, true); + + // If we make g no longer a debuggee, then trying to touch the frame at + // all show throw. + dbg.removeDebuggee(g); + assertThrowsInstanceOf(() => frame.script, Error); + assertThrowsInstanceOf(() => frame.environment, Error); +} + +g.eval('function f() { debugger; }'); + +log = ''; +dbg.addDebuggee(g); +g.f(); +assertEq(log, 'call'); + +log = ''; +dbg.addDebuggee(g); +g.eval('debugger;'); +assertEq(log, 'eval'); diff --git a/js/src/jit-test/tests/debug/Frame-this-01.js b/js/src/jit-test/tests/debug/Frame-this-01.js new file mode 100644 index 000000000..3a610a7c5 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-this-01.js @@ -0,0 +1,24 @@ +// Frame.prototype.this in strict-mode functions, with primitive values + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + hits++; + assertEq(frame.this, g.v); +}; + +g.eval("function f() { 'use strict'; debugger; }"); + +g.eval("Boolean.prototype.f = f; v = true; v.f();"); +g.eval("f.call(v);"); +g.eval("Number.prototype.f = f; v = 3.14; v.f();"); +g.eval("f.call(v);"); +g.eval("String.prototype.f = f; v = 'hello'; v.f();"); +g.eval("f.call(v);"); +g.eval("Symbol.prototype.f = f; v = Symbol('world'); v.f();"); +g.eval("f.call(v);"); +g.eval("v = undefined; f.call(v);"); +g.eval("v = null; f.call(v);"); + +assertEq(hits, 10); diff --git a/js/src/jit-test/tests/debug/Frame-this-02.js b/js/src/jit-test/tests/debug/Frame-this-02.js new file mode 100644 index 000000000..8f60a5f46 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-this-02.js @@ -0,0 +1,17 @@ +// Frame.prototype.this in strict direct eval frames + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + hits++; + assertEq(frame.this, g.v); +}; + +g.eval("function f() { 'use strict'; eval('debugger;'); }"); + +g.eval("Boolean.prototype.f = f; v = true; v.f();"); +g.eval("f.call(v);"); +g.eval("v = null; f.call(v);"); + +assertEq(hits, 3); diff --git a/js/src/jit-test/tests/debug/Frame-this-03.js b/js/src/jit-test/tests/debug/Frame-this-03.js new file mode 100644 index 000000000..fbd55c6e0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-this-03.js @@ -0,0 +1,29 @@ +// Frame.prototype.this in non-strict-mode functions, with primitive values + +function classOf(obj) { + return Object.prototype.toString.call(obj).match(/^\[object (.*)\]$/)[1]; +} + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + hits++; + assertEq(frame.this instanceof Debugger.Object, true); + assertEq(frame.this.class, g.v == null ? classOf(g) : classOf(Object(g.v))); +}; + +g.eval("function f() { debugger; }"); + +g.eval("Boolean.prototype.f = f; v = true; v.f();"); +g.eval("f.call(v);"); +g.eval("Number.prototype.f = f; v = 3.14; v.f();"); +g.eval("f.call(v);"); +g.eval("String.prototype.f = f; v = 'hello'; v.f();"); +g.eval("f.call(v);"); +g.eval("Symbol.prototype.f = f; v = Symbol('world'); v.f();"); +g.eval("f.call(v);"); +g.eval("v = undefined; f.call(v);"); +g.eval("v = null; f.call(v);"); + +assertEq(hits, 10); diff --git a/js/src/jit-test/tests/debug/Frame-this-04.js b/js/src/jit-test/tests/debug/Frame-this-04.js new file mode 100644 index 000000000..cfa3f5df9 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-this-04.js @@ -0,0 +1,25 @@ +// Debugger.Frame.prototype.this in functions, with object values + +function classOf(obj) { + return Object.prototype.toString.call(obj).match(/^\[object (.*)\]$/)[1]; +} + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + hits++; + assertEq(frame.this instanceof Debugger.Object, true); + assertEq(frame.this.class, classOf(Object(g.v))); +}; + +g.eval("function f() { debugger; }"); + +g.eval("v = {}; f.call(v);"); +g.eval("v.f = f; v.f();"); +g.eval("v = new Date; f.call(v);"); +g.eval("v.f = f; v.f();"); +g.eval("v = []; f.call(v);"); +g.eval("Object.prototype.f = f; v.f();"); +g.eval("v = this; f();"); +assertEq(hits, 7); diff --git a/js/src/jit-test/tests/debug/Frame-this-05.js b/js/src/jit-test/tests/debug/Frame-this-05.js new file mode 100644 index 000000000..bd404d240 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-this-05.js @@ -0,0 +1,23 @@ +// Frame.this and evalInFrame in the global scope. +var g = newGlobal(); +g.eval("x = 4; this['.this'] = 222;"); +var dbg = new Debugger(g); +var res; +dbg.onDebuggerStatement = function (frame) { + res = frame.eval("this.x").return; + res += frame.this.unsafeDereference().x; +}; +g.eval("debugger;"); +assertEq(res, 8); + +// And inside eval. +g.eval("x = 3; eval('debugger')"); +assertEq(res, 6); +g.eval("x = 2; eval('eval(\\'debugger\\')')"); +assertEq(res, 4); + +// And inside arrow functions. +g.eval("x = 1; (() => { debugger; })()"); +assertEq(res, 2); +g.eval("x = 5; (() => { eval('debugger'); })()"); +assertEq(res, 10); diff --git a/js/src/jit-test/tests/debug/Frame-this-06.js b/js/src/jit-test/tests/debug/Frame-this-06.js new file mode 100644 index 000000000..04dc3e5d1 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-this-06.js @@ -0,0 +1,22 @@ +// Frame.this and evalInFrame with missing this, strict and non-strict. +var g = newGlobal(); +var dbg = new Debugger(g); +var evalThis, frameThis; +dbg.onEnterFrame = function (frame) { + if (frame.type === "eval") + return; + assertEq(frame.type, "call"); + evalThis = frame.eval("this"); + frameThis = frame.this; +}; + +// Strict, this is primitive. +g.eval("var foo = function() { 'use strict'; }; foo.call(33);"); +assertEq(evalThis.return, 33); +assertEq(frameThis, 33); + +// Non-strict, this has to be boxed. +g.eval("var bar = function() { }; bar.call(22);"); +assertEq(typeof evalThis.return, "object"); +assertEq(evalThis.return.unsafeDereference().valueOf(), 22); +assertEq(frameThis.unsafeDereference().valueOf(), 22); diff --git a/js/src/jit-test/tests/debug/Frame-this-07.js b/js/src/jit-test/tests/debug/Frame-this-07.js new file mode 100644 index 000000000..c7bc41272 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-this-07.js @@ -0,0 +1,19 @@ +// Frame.this can be marked as optimized-out in some cases. Here we call an +// arrow function but its enclosing function is no longer live, so it's +// impossible to recover its missing 'this' binding. +var g = newGlobal(); +g.eval("x = 4"); +g.eval("var foo = function() { return () => 1; }; var arrow = foo.call(3);"); +var dbg = new Debugger(g); +var log = ""; +dbg.onEnterFrame = function (frame) { + if (frame.type === "eval") + return; + assertEq(frame.type, "call"); + assertEq(frame.this.optimizedOut, true); + frame.eval("try { print(this.x); } catch(e) { exc = e; }"); + assertEq(typeof g.exc, "object"); + log += "d"; +}; +g.eval("arrow();"); +assertEq(log, "d"); diff --git a/js/src/jit-test/tests/debug/Frame-this-08.js b/js/src/jit-test/tests/debug/Frame-this-08.js new file mode 100644 index 000000000..493214e3b --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-this-08.js @@ -0,0 +1,16 @@ +// Frame.this and evalInFrame in arrow function that uses 'this'. +var g = newGlobal(); +g.eval("x = 4"); +g.eval("var foo = function() { 'use strict'; return () => this; }; var arrow = foo.call(3);"); +var dbg = new Debugger(g); +var hits = 0; +dbg.onEnterFrame = function (frame) { + if (frame.type === "eval") + return; + hits++; + assertEq(frame.type, "call"); + assertEq(frame.this, 3); + assertEq(frame.eval("this + 1").return, 4); +}; +g.eval("arrow();"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Frame-this-09.js b/js/src/jit-test/tests/debug/Frame-this-09.js new file mode 100644 index 000000000..7ad90fe8c --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-this-09.js @@ -0,0 +1,45 @@ +// Ensure |Frame.this| returns the right value even if we're still in the +// script's prologue, before JSOP_FUNCTIONTHIS. + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onEnterFrame = function (frame) { + if (frame.type === 'eval') + return; + hits++; + + var frameThis = frame.this; + if (frameThis !== null && typeof frameThis === "object") + g.gotThis = frameThis.unsafeDereference(); + else + g.gotThis = frameThis; + + assertEq(frame.this, frameThis, "getter should not return a different object"); +}; + +// Strict mode, function uses |this|. +g.eval("function strictfun() { 'use strict'; return this; }"); +g.eval("strictfun.call(Math); assertEq(gotThis, Math);"); +g.eval("strictfun.call(true); assertEq(gotThis, true);"); +g.eval("strictfun.call(); assertEq(gotThis, undefined);"); + +// Strict mode, function doesn't use |this|. +g.eval("function strictfunNoThis() { 'use strict'; }"); +g.eval("strictfunNoThis.call(Math); assertEq(gotThis, Math);"); +g.eval("strictfunNoThis.call(true); assertEq(gotThis, true);"); +g.eval("strictfunNoThis.call(null); assertEq(gotThis, null);"); + +// Non-strict mode (primitive |this| is boxed), function uses |this|. +g.eval("function nonstrictfun() { return this; }"); +g.eval("nonstrictfun.call(Math); assertEq(gotThis, Math);"); +g.eval("nonstrictfun.call(null); assertEq(gotThis, this);"); +g.eval("nonstrictfun.call(); assertEq(gotThis, this);"); + +// Non-strict mode (primitive |this| is boxed), function doesn't use |this|. +g.eval("function nonstrictfunNoThis() {}"); +g.eval("nonstrictfunNoThis.call(Math); assertEq(gotThis, Math);"); +g.eval("nonstrictfunNoThis.call(null); assertEq(gotThis, this);"); +g.eval("nonstrictfunNoThis.call(); assertEq(gotThis, this);"); + +assertEq(hits, 12); diff --git a/js/src/jit-test/tests/debug/Frame-this-10.js b/js/src/jit-test/tests/debug/Frame-this-10.js new file mode 100644 index 000000000..90a833c98 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-this-10.js @@ -0,0 +1,42 @@ +// Check the Frame.this getter always returns the same object for a given frame. +// Primitive this-values should not be boxed multiple times. + +var g = newGlobal(); +var dbg = new Debugger(g); +var framesEntered = 0; +var framesPopped = 0; +var numSteps = 0; +dbg.onEnterFrame = function (frame) { + if (frame.type === 'eval') + return; + framesEntered++; + + var frameThis = frame.this; + assertEq(frame.this, frameThis); + + frame.onPop = function() { + framesPopped++; + assertEq(frame.this, frameThis); + }; + + frame.onStep = function() { + numSteps++; + assertEq(frame.this, frameThis); + } + + g.gotThis = frameThis.unsafeDereference(); +}; + +g.eval("function nonstrictfun() { return this; }"); +g.eval("nonstrictfun.call(Math); assertEq(gotThis, Math);"); +g.eval("nonstrictfun.call(true); assertEq(gotThis.valueOf(), true);"); +g.eval("nonstrictfun.call(); assertEq(gotThis, this);"); + +g.eval("function nonstrictfunNoThis() { return 1; }"); +g.eval("nonstrictfunNoThis.call(Math); assertEq(gotThis, Math);"); +g.eval("nonstrictfunNoThis.call(true); assertEq(gotThis.valueOf(), true);"); +g.eval("nonstrictfunNoThis.call(); assertEq(gotThis, this);"); + +assertEq(framesEntered, 6); +assertEq(framesPopped, 6); +assertEq(numSteps > 15, true); diff --git a/js/src/jit-test/tests/debug/Frame-this-11.js b/js/src/jit-test/tests/debug/Frame-this-11.js new file mode 100644 index 000000000..6ab8ccba3 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-this-11.js @@ -0,0 +1,46 @@ +// Ensure evalInFrame("this") returns the right value even if we're still in the +// script's prologue, before JSOP_FUNCTIONTHIS. + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onEnterFrame = function (frame) { + if (frame.type === 'eval') + return; + hits++; + + var frameThis = frame.eval('this').return; + if (frameThis !== null && typeof frameThis === "object") + g.gotThis = frameThis.unsafeDereference(); + else + g.gotThis = frameThis; + + assertEq(frame.this, frameThis); + assertEq(frame.eval('this').return, frameThis); +}; + +// Strict mode, function uses |this|. +g.eval("function strictfun() { 'use strict'; return this; }"); +g.eval("strictfun.call(Math); assertEq(gotThis, Math);"); +g.eval("strictfun.call(true); assertEq(gotThis, true);"); +g.eval("strictfun.call(); assertEq(gotThis, undefined);"); + +// Strict mode, function doesn't use |this|. +g.eval("function strictfunNoThis() { 'use strict'; }"); +g.eval("strictfunNoThis.call(Math); assertEq(gotThis, Math);"); +g.eval("strictfunNoThis.call(true); assertEq(gotThis, true);"); +g.eval("strictfunNoThis.call(null); assertEq(gotThis, null);"); + +// Non-strict mode (primitive |this| is boxed), function uses |this|. +g.eval("function nonstrictfun() { return this; }"); +g.eval("nonstrictfun.call(Math); assertEq(gotThis, Math);"); +g.eval("nonstrictfun.call(null); assertEq(gotThis, this);"); +g.eval("nonstrictfun.call(); assertEq(gotThis, this);"); + +// Non-strict mode (primitive |this| is boxed), function doesn't use |this|. +g.eval("function nonstrictfunNoThis() {}"); +g.eval("nonstrictfunNoThis.call(Math); assertEq(gotThis, Math);"); +g.eval("nonstrictfunNoThis.call(null); assertEq(gotThis, this);"); +g.eval("nonstrictfunNoThis.call(); assertEq(gotThis, this);"); + +assertEq(hits, 12); diff --git a/js/src/jit-test/tests/debug/Frame-this-12.js b/js/src/jit-test/tests/debug/Frame-this-12.js new file mode 100644 index 000000000..625fa9df5 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-this-12.js @@ -0,0 +1,42 @@ +// Check evalInFrame("this") always returns the same object for a given frame. +// Primitive this-values should not be boxed multiple times. + +var g = newGlobal(); +var dbg = new Debugger(g); +var framesEntered = 0; +var framesPopped = 0; +var numSteps = 0; +dbg.onEnterFrame = function (frame) { + if (frame.type === 'eval') + return; + framesEntered++; + + var frameThis = frame.eval('this').return; + + frame.onPop = function() { + framesPopped++; + assertEq(frame.eval('this').return, frameThis); + }; + + frame.onStep = function() { + numSteps++; + assertEq(frame.eval('this').return, frameThis); + } + + g.gotThis = frameThis.unsafeDereference(); + assertEq(frame.this, frameThis); +}; + +g.eval("function nonstrictfun() { return this; }"); +g.eval("nonstrictfun.call(Math); assertEq(gotThis, Math);"); +g.eval("nonstrictfun.call(true); assertEq(gotThis.valueOf(), true);"); +g.eval("nonstrictfun.call(); assertEq(gotThis, this);"); + +g.eval("function nonstrictfunNoThis() { return 1; }"); +g.eval("nonstrictfunNoThis.call(Math); assertEq(gotThis, Math);"); +g.eval("nonstrictfunNoThis.call(true); assertEq(gotThis.valueOf(), true);"); +g.eval("nonstrictfunNoThis.call(); assertEq(gotThis, this);"); + +assertEq(framesEntered, 6); +assertEq(framesPopped, 6); +assertEq(numSteps > 15, true); diff --git a/js/src/jit-test/tests/debug/Memory-01.js b/js/src/jit-test/tests/debug/Memory-01.js new file mode 100644 index 000000000..7dc00b9ee --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-01.js @@ -0,0 +1,6 @@ +assertEq(typeof Debugger.Memory, "function"); +let dbg = new Debugger; +assertEq(dbg.memory instanceof Debugger.Memory, true); + +load(libdir + "asserts.js"); +assertThrowsInstanceOf(() => new Debugger.Memory, TypeError); diff --git a/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-01.js b/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-01.js new file mode 100644 index 000000000..6575cfe74 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-01.js @@ -0,0 +1,45 @@ +// Test that setting Debugger.Memory.prototype.allocationSamplingProbability to +// a bad number throws. + +load(libdir + "asserts.js"); + +const root = newGlobal(); + +const dbg = new Debugger(); +const wrappedRoot = dbg.addDebuggee(root); + +var mem = dbg.memory; + +// Out of range, negative +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = -Number.MAX_VALUE, + TypeError); +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = -1, + TypeError); +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = -Number.MIN_VALUE, + TypeError); + +// In range +mem.allocationSamplingProbability = -0.0; +mem.allocationSamplingProbability = 0.0; +mem.allocationSamplingProbability = Number.MIN_VALUE; +mem.allocationSamplingProbability = 1 / 3; +mem.allocationSamplingProbability = .5; +mem.allocationSamplingProbability = 2 / 3; +mem.allocationSamplingProbability = 1 - Math.pow(2, -53); +mem.allocationSamplingProbability = 1; + +// Out of range, positive +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = 1 + Number.EPSILON, + TypeError); +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = 2, + TypeError); +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = Number.MAX_VALUE, + TypeError); + +// Out of range, non-finite +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = -Infinity, + TypeError); +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = Infinity, + TypeError); +assertThrowsInstanceOf(() => mem.allocationSamplingProbability = NaN, + TypeError); diff --git a/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-02.js b/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-02.js new file mode 100644 index 000000000..cd8c7df77 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-allocationSamplingProbability-02.js @@ -0,0 +1,36 @@ +// Test that we only sample about allocationSamplingProbability * 100 percent of +// allocations. + +const root = newGlobal(); + +const dbg = new Debugger(); +const wrappedRoot = dbg.addDebuggee(root); + +root.eval(` + objs = []; + objs.push(new Object); +`); + +root.eval("" + function makeSomeAllocations() { + for (var i = 0; i < 100; i++) { + objs.push(new Object); + } +}); + +function measure(P, expected) { + root.setSavedStacksRNGState(Number.MAX_SAFE_INTEGER - 1); + dbg.memory.allocationSamplingProbability = P; + root.makeSomeAllocations(); + assertEq(dbg.memory.drainAllocationsLog().length, expected); +} + +dbg.memory.trackingAllocationSites = true; + +// These are the sample counts that were correct when this test was last +// updated; changes to SpiderMonkey may occasionally cause changes +// here. Anything that is within a plausible range for the given sampling +// probability is fine. +measure(0.0, 0); +measure(1.0, 100); +measure(0.1, 11); +measure(0.5, 49); diff --git a/js/src/jit-test/tests/debug/Memory-allocationsLogOverflowed-01.js b/js/src/jit-test/tests/debug/Memory-allocationsLogOverflowed-01.js new file mode 100644 index 000000000..920fcff94 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-allocationsLogOverflowed-01.js @@ -0,0 +1,24 @@ +// Test basic usage of `Debugger.Memory.prototype.allocationsLogOverflowed`. + +const root = newGlobal(); +const dbg = new Debugger(root); +dbg.memory.trackingAllocationSites = true; +dbg.memory.maxAllocationsLogLength = 1; + +root.eval("(" + function immediate() { + // Allocate more than the max log length. + this.objs = [{}, {}, {}, {}]; +} + "());"); + +// The log should have overflowed. +assertEq(dbg.memory.allocationsLogOverflowed, true); + +// Once drained, the flag should be reset. +const allocs = dbg.memory.drainAllocationsLog(); +assertEq(dbg.memory.allocationsLogOverflowed, false); + +// If we keep allocations under the max log length, then we shouldn't have +// overflowed. +dbg.memory.maxAllocationsLogLength = 10000; +root.eval("this.objs = [{}, {}, {}, {}];"); +assertEq(dbg.memory.allocationsLogOverflowed, false); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-01.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-01.js new file mode 100644 index 000000000..91aa07864 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-01.js @@ -0,0 +1,31 @@ +// Test basic usage of drainAllocationsLog() + +const root = newGlobal(); +const dbg = new Debugger(); +const wrappedRoot = dbg.addDebuggee(root) +dbg.memory.trackingAllocationSites = true; + +root.eval("(" + function immediate() { + this.tests = [ + ({}), + [], + /(two|2)\s*problems/, + new function Ctor(){}, + new Object(), + new Array(), + new Date(), + ]; +} + "());"); + +const allocs = dbg.memory.drainAllocationsLog(); +print(allocs.join("\n--------------------------------------------------------------------------\n")); +print("Total number of allocations logged: " + allocs.length); + +let idx = -1; +for (let object of root.tests) { + let wrappedObject = wrappedRoot.makeDebuggeeValue(object); + let allocSite = wrappedObject.allocationSite; + let newIdx = allocs.map(x => x.frame).indexOf(allocSite); + assertEq(newIdx > idx, true); + idx = newIdx; +} diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-02.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-02.js new file mode 100644 index 000000000..e9d56bf10 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-02.js @@ -0,0 +1,14 @@ +// Test that drainAllocationsLog fails when we aren't trackingAllocationSites. + +load(libdir + 'asserts.js'); + +const root = newGlobal(); +const dbg = new Debugger(); + +dbg.memory.trackingAllocationSites = true; +root.eval("this.alloc1 = {}"); +dbg.memory.trackingAllocationSites = false; +root.eval("this.alloc2 = {};"); + +assertThrowsInstanceOf(() => dbg.memory.drainAllocationsLog(), + Error); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-03.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-03.js new file mode 100644 index 000000000..064aed5cb --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-03.js @@ -0,0 +1,24 @@ +// Test when there are more allocations than the maximum log length. + +const root = newGlobal(); +const dbg = new Debugger(); +dbg.addDebuggee(root) + +dbg.memory.maxAllocationsLogLength = 3; +dbg.memory.trackingAllocationSites = true; + +root.eval([ + "this.alloc1 = {};", // line 1 + "this.alloc2 = {};", // line 2 + "this.alloc3 = {};", // line 3 + "this.alloc4 = {};", // line 4 +].join("\n")); + +const allocs = dbg.memory.drainAllocationsLog(); + +// Should have stayed at the maximum length. +assertEq(allocs.length, 3); +// Should have kept the most recent allocation. +assertEq(allocs[2].frame.line, 4); +// Should have thrown away the oldest allocation. +assertEq(allocs.map(x => x.frame.line).indexOf(1), -1); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-04.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-04.js new file mode 100644 index 000000000..169db55d0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-04.js @@ -0,0 +1,21 @@ +// Test that when we shorten the maximum log length, we won't get a longer log +// than that new maximum afterwards. + +const root = newGlobal(); +const dbg = new Debugger(); +dbg.addDebuggee(root) + +dbg.memory.trackingAllocationSites = true; + +root.eval([ + "this.alloc1 = {};", // line 1 + "this.alloc2 = {};", // line 2 + "this.alloc3 = {};", // line 3 + "this.alloc4 = {};", // line 4 +].join("\n")); + +dbg.memory.maxAllocationsLogLength = 1; +const allocs = dbg.memory.drainAllocationsLog(); + +// Should have trimmed down to the new maximum length. +assertEq(allocs.length, 1); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-05.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-05.js new file mode 100644 index 000000000..0e8e035da --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-05.js @@ -0,0 +1,9 @@ +// Test an empty allocation log. + +const root = newGlobal(); +const dbg = new Debugger(); +dbg.addDebuggee(root) + +dbg.memory.trackingAllocationSites = true; +const allocs = dbg.memory.drainAllocationsLog(); +assertEq(allocs.length, 0); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-06.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-06.js new file mode 100644 index 000000000..3eb6a97bb --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-06.js @@ -0,0 +1,23 @@ +// Test doing a GC while we have a non-empty log. + +const root = newGlobal(); +const dbg = new Debugger(); +dbg.addDebuggee(root) +dbg.memory.trackingAllocationSites = true; + +root.eval("(" + function immediate() { + this.tests = [ + ({}), + [], + /(two|2)\s*problems/, + new function Ctor(){}, + new Object(), + new Array(), + new Date(), + ]; +} + "());"); + +gc(); + +const allocs = dbg.memory.drainAllocationsLog(); +assertEq(allocs.length >= root.tests.length, true); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-07.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-07.js new file mode 100644 index 000000000..fed52aac6 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-07.js @@ -0,0 +1,10 @@ +// Test retrieving the log when allocation tracking hasn't ever been enabled. + +load(libdir + 'asserts.js'); + +const root = newGlobal(); +const dbg = new Debugger(); +dbg.addDebuggee(root) + +assertThrowsInstanceOf(() => dbg.memory.drainAllocationsLog(), + Error); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-08.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-08.js new file mode 100644 index 000000000..e9f2fb41c --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-08.js @@ -0,0 +1,30 @@ +// Test retrieving the log multiple times. + +const root = newGlobal(); +const dbg = new Debugger(); +dbg.addDebuggee(root) + +root.eval([ + "this.allocs = [];", + "this.doFirstAlloc = " + function () { + this.allocs.push({}); this.firstAllocLine = Error().lineNumber; + }, + "this.doSecondAlloc = " + function () { + this.allocs.push(new Object()); this.secondAllocLine = Error().lineNumber; + } +].join("\n")); + +dbg.memory.trackingAllocationSites = true; + +root.doFirstAlloc(); +let allocs1 = dbg.memory.drainAllocationsLog(); +root.doSecondAlloc(); +let allocs2 = dbg.memory.drainAllocationsLog(); + +let allocs1Lines = allocs1.filter(x => !!x.frame).map(x => x.frame.line); +assertEq(allocs1Lines.indexOf(root.firstAllocLine) != -1, true); +assertEq(allocs1Lines.indexOf(root.secondAllocLine) == -1, true); + +let allocs2Lines = allocs2.filter(x => !!x.frame).map(x => x.frame.line); +assertEq(allocs2Lines.indexOf(root.secondAllocLine) != -1, true); +assertEq(allocs2Lines.indexOf(root.firstAllocLine) == -1, true); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-09.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-09.js new file mode 100644 index 000000000..d69249109 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-09.js @@ -0,0 +1,20 @@ +// Test logs that contain allocations from many debuggee compartments. + +const dbg = new Debugger(); + +const root1 = newGlobal(); +const root2 = newGlobal(); +const root3 = newGlobal(); + +dbg.addDebuggee(root1); +dbg.addDebuggee(root2); +dbg.addDebuggee(root3); + +dbg.memory.trackingAllocationSites = true; + +root1.eval("this.alloc = {}"); +root2.eval("this.alloc = {}"); +root3.eval("this.alloc = {}"); + +const allocs = dbg.memory.drainAllocationsLog(); +assertEq(allocs.length >= 3, true); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-10.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-10.js new file mode 100644 index 000000000..42e133128 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-10.js @@ -0,0 +1,21 @@ +// Test logs that contain allocations from debuggee compartments added as we are +// logging. + +const dbg = new Debugger(); + +dbg.memory.trackingAllocationSites = true; + +const root1 = newGlobal(); +dbg.addDebuggee(root1); +root1.eval("this.alloc = {}"); + +const root2 = newGlobal(); +dbg.addDebuggee(root2); +root2.eval("this.alloc = {}"); + +const root3 = newGlobal(); +dbg.addDebuggee(root3); +root3.eval("this.alloc = {}"); + +const allocs = dbg.memory.drainAllocationsLog(); +assertEq(allocs.length >= 3, true); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-11.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-11.js new file mode 100644 index 000000000..ff993b45f --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-11.js @@ -0,0 +1,25 @@ +// Test logs that shouldn't contain allocations from debuggee compartments +// removed as we are logging. + +const dbg = new Debugger(); + +const root1 = newGlobal(); +dbg.addDebuggee(root1); +const root2 = newGlobal(); +dbg.addDebuggee(root2); +const root3 = newGlobal(); +dbg.addDebuggee(root3); + +dbg.memory.trackingAllocationSites = true; + +dbg.removeDebuggee(root1); +root1.eval("this.alloc = {}"); + +dbg.removeDebuggee(root2); +root2.eval("this.alloc = {}"); + +dbg.removeDebuggee(root3); +root3.eval("this.alloc = {}"); + +const allocs = dbg.memory.drainAllocationsLog(); +assertEq(allocs.length, 0); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-12.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-12.js new file mode 100644 index 000000000..ed1c4e5f6 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-12.js @@ -0,0 +1,17 @@ +// Test that disabling the debugger disables allocation tracking. + +load(libdir + "asserts.js"); + +const dbg = new Debugger(); +const root = newGlobal(); +dbg.addDebuggee(root); + +dbg.memory.trackingAllocationSites = true; +dbg.enabled = false; + +root.eval("this.alloc = {}"); + +// We shouldn't accumulate allocations in our log while the debugger is +// disabled. +let allocs = dbg.memory.drainAllocationsLog(); +assertEq(allocs.length, 0); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-13.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-13.js new file mode 100644 index 000000000..4ac6b26a5 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-13.js @@ -0,0 +1,18 @@ +// Test that we don't crash while logging allocations and there is +// off-main-thread compilation. OMT compilation will allocate functions and +// regexps, but we just punt on measuring that accurately. + +if (helperThreadCount() === 0) { + quit(0); +} + +const root = newGlobal(); +root.eval("this.dbg = new Debugger()"); +root.dbg.addDebuggee(this); +root.dbg.memory.trackingAllocationSites = true; + +offThreadCompileScript( + "function foo() {\n" + + " print('hello world');\n" + + "}" +); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-14.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-14.js new file mode 100644 index 000000000..dff8c8c0a --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-14.js @@ -0,0 +1,47 @@ +// Test that drainAllocationsLog returns some timestamps. + +load(libdir + 'asserts.js'); + +var allocTimes = []; + +allocTimes.push(dateNow()); + +const root = newGlobal(); +const dbg = new Debugger(root); + +dbg.memory.trackingAllocationSites = true; +root.eval("this.alloc1 = {}"); +allocTimes.push(dateNow()); +root.eval("this.alloc2 = {}"); +allocTimes.push(dateNow()); +root.eval("this.alloc3 = {}"); +allocTimes.push(dateNow()); +root.eval("this.alloc4 = {}"); +allocTimes.push(dateNow()); + +allocs = dbg.memory.drainAllocationsLog(); +assertEq(allocs.length >= 4, true); +assertEq(allocs[0].timestamp >= allocTimes[0], true); +var seenAlloc = 0; +var lastIndexSeenAllocIncremented = 0; +for (i = 1; i < allocs.length; ++i) { + assertEq(allocs[i].timestamp >= allocs[i - 1].timestamp, true); + // It isn't possible to exactly correlate the entries in the + // allocs array with the entries in allocTimes, because we can't + // control exactly how many allocations are done during the course + // of a given eval. However, we can assume that there is some + // allocation recorded after each entry in allocTimes. So, we + // track the allocTimes entry we've passed, and then after the + // loop assert that we've seen them all. We also assert that a + // non-zero number of allocations has happened since the last seen + // increment. + while (seenAlloc < allocTimes.length + && allocs[i].timestamp >= allocTimes[seenAlloc]) { + assertEq(i - lastIndexSeenAllocIncremented > 0, true); + lastIndexSeenAllocIncremented = i; + ++seenAlloc; + } +} +// There should be one entry left in allocTimes, because we recorded a +// time after the last possible allocation in the array. +assertEq(seenAlloc, allocTimes.length -1); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-15.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-15.js new file mode 100644 index 000000000..471e8ce39 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-15.js @@ -0,0 +1,33 @@ +// Test drainAllocationsLog() and [[Class]] names. +if (!('Promise' in this)) + quit(0); + +const root = newGlobal(); +const dbg = new Debugger(); +const wrappedRoot = dbg.addDebuggee(root) + +root.eval( + ` + this.tests = [ + { expected: "Object", test: () => new Object }, + { expected: "Array", test: () => [] }, + { expected: "Date", test: () => new Date }, + { expected: "RegExp", test: () => /problems/ }, + { expected: "Int8Array", test: () => new Int8Array }, + { expected: "Promise", test: () => new Promise(function (){})}, + ]; + ` +); + + +for (let { expected, test } of root.tests) { + print(expected); + + dbg.memory.trackingAllocationSites = true; + test(); + let allocs = dbg.memory.drainAllocationsLog(); + dbg.memory.trackingAllocationSites = false; + + assertEq(allocs.some(a => a.class === expected), true); +} + diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-16.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-16.js new file mode 100644 index 000000000..b147d6ded --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-16.js @@ -0,0 +1,47 @@ +// Test drainAllocationsLog() and constructor names. + +const root = newGlobal(); +const dbg = new Debugger(); +const wrappedRoot = dbg.addDebuggee(root); + +root.eval( + ` + function Ctor() {} + + var nested = {}; + nested.Ctor = function () {}; + + function makeInstance() { + let LexicalCtor = function () {}; + return new LexicalCtor; + } + + function makeObject() { + let object = {}; + return object; + } + + this.tests = [ + { name: "Ctor", fn: () => new Ctor }, + { name: "nested.Ctor", fn: () => new nested.Ctor }, + { name: "makeInstance/LexicalCtor", fn: () => makeInstance() }, + { name: null, fn: () => ({}) }, + { name: null, fn: () => (nested.object = {}) }, + { name: null, fn: () => makeObject() }, + ]; + ` +); + +for (let { name, fn } of root.tests) { + print(name); + + dbg.memory.trackingAllocationSites = true; + + fn(); + + let entries = dbg.memory.drainAllocationsLog(); + let ctors = entries.map(e => e.constructor); + assertEq(ctors.some(ctor => ctor === name), true); + + dbg.memory.trackingAllocationSites = false; +} diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-17.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-17.js new file mode 100644 index 000000000..c48afc0f5 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-17.js @@ -0,0 +1,55 @@ +// Test drainAllocationsLog() and byte sizes. + +const root = newGlobal(); +const dbg = new Debugger(); +const wrappedRoot = dbg.addDebuggee(root); + +root.eval( + ` + function AsmModule(stdlib, foreign, heap) { + "use asm"; + + function test() { + return 5|0; + } + + return { test: test }; + } + const buf = new ArrayBuffer(1024*8); + + function Ctor() {} + this.tests = [ + { name: "new UInt8Array(256)", fn: () => new Uint8Array(256) }, + { name: "arguments", fn: function () { return arguments; } }, + { name: "asm.js module", fn: () => AsmModule(this, {}, buf) }, + { name: "/2manyproblemz/g", fn: () => /2manyproblemz/g }, + { name: "iterator", fn: () => [1,2,3][Symbol.iterator]() }, + { name: "Error()", fn: () => Error() }, + { name: "new Ctor", fn: () => new Ctor }, + { name: "{}", fn: () => ({}) }, + { name: "new Date", fn: () => new Date }, + { name: "[1,2,3]", fn: () => [1,2,3] }, + ]; + ` +); + +for (let { name, fn } of root.tests) { + print("Test: " + name); + + dbg.memory.trackingAllocationSites = true; + + fn(); + + let entries = dbg.memory.drainAllocationsLog(); + + for (let {size} of entries) { + print(" " + size + " bytes"); + // We should get some kind of byte size. We aren't testing that in depth + // here, it is tested pretty thoroughly in + // js/src/jit-test/tests/heap-analysis/byteSize-of-object.js. + assertEq(typeof size, "number"); + assertEq(size > 0, true); + } + + dbg.memory.trackingAllocationSites = false; +} diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-18.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-18.js new file mode 100644 index 000000000..614c4e559 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-18.js @@ -0,0 +1,27 @@ +// Test drainAllocationsLog() entries' inNursery flag. + +const root = newGlobal(); +const dbg = new Debugger(); +const wrappedRoot = dbg.addDebuggee(root); + +dbg.memory.trackingAllocationSites = true; + +root.eval( + ` + for (let i = 0; i < 10; i++) + allocationMarker({ nursery: true }); + + for (let i = 0; i < 10; i++) + allocationMarker({ nursery: false }); + ` +); + +let entries = dbg.memory.drainAllocationsLog().filter(e => e.class == "AllocationMarker"); + +assertEq(entries.length, 20); + +for (let i = 0; i < 10; i++) + assertEq(entries[i].inNursery, true); + +for (let i = 10; i < 20; i++) + assertEq(entries[i].inNursery, false); diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-01.js b/js/src/jit-test/tests/debug/Memory-takeCensus-01.js new file mode 100644 index 000000000..1ca3649aa --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-01.js @@ -0,0 +1,23 @@ +// Debugger.Memory.prototype.takeCensus returns a value of an appropriate shape. + +var dbg = new Debugger; + +function checkProperties(census) { + assertEq(typeof census, 'object'); + for (prop of Object.getOwnPropertyNames(census)) { + var desc = Object.getOwnPropertyDescriptor(census, prop); + assertEq(desc.enumerable, true); + assertEq(desc.configurable, true); + assertEq(desc.writable, true); + if (typeof desc.value === 'object') + checkProperties(desc.value); + else + assertEq(typeof desc.value, 'number'); + } +} + +checkProperties(dbg.memory.takeCensus()); + +var g = newGlobal(); +dbg.addDebuggee(g); +checkProperties(dbg.memory.takeCensus()); diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-02.js b/js/src/jit-test/tests/debug/Memory-takeCensus-02.js new file mode 100644 index 000000000..14cf9e4e7 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-02.js @@ -0,0 +1,49 @@ +// Debugger.Memory.prototype.takeCensus behaves plausibly as we allocate objects. + +// Exact object counts vary in ways we can't predict. For example, +// BaselineScripts can hold onto "template objects", which exist only to hold +// the shape and type for newly created objects. When BaselineScripts are +// discarded, these template objects go with them. +// +// So instead of expecting precise counts, we expect counts that are at least as +// many as we would expect given the object graph we've built. + +load(libdir + 'census.js'); + +// A Debugger with no debuggees had better not find anything. +var dbg = new Debugger; +var census0 = dbg.memory.takeCensus(); +Census.walkCensus(census0, "census0", Census.assertAllZeros); + +function newGlobalWithDefs() { + var g = newGlobal(); + g.eval(` + function times(n, fn) { + var a=[]; + for (var i = 0; i<n; i++) + a.push(fn()); + return a; + }`); + return g; +} + +// Allocate a large number of various types of objects, and check that census +// finds them. +var g = newGlobalWithDefs(); +dbg.addDebuggee(g); + +g.eval('var objs = times(100, () => ({}));'); +g.eval('var rxs = times(200, () => /foo/);'); +g.eval('var ars = times(400, () => []);'); +g.eval('var fns = times(800, () => () => {});'); + +var census1 = dbg.memory.takeCensus(); +Census.walkCensus(census1, "census1", + Census.assertAllNotLessThan( + { 'objects': + { 'Object': { count: 100 }, + 'RegExp': { count: 200 }, + 'Array': { count: 400 }, + 'Function': { count: 800 } + } + })); diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-03.js b/js/src/jit-test/tests/debug/Memory-takeCensus-03.js new file mode 100644 index 000000000..dda6e6b2f --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-03.js @@ -0,0 +1,26 @@ +// Debugger.Memory.prototype.takeCensus behaves plausibly as we add and remove debuggees. + +load(libdir + 'census.js'); + +var dbg = new Debugger; + +var census0 = dbg.memory.takeCensus(); +Census.walkCensus(census0, "census0", Census.assertAllZeros); + +var g1 = newGlobal(); +dbg.addDebuggee(g1); +var census1 = dbg.memory.takeCensus(); +Census.walkCensus(census1, "census1", Census.assertAllNotLessThan(census0)); + +var g2 = newGlobal(); +dbg.addDebuggee(g2); +var census2 = dbg.memory.takeCensus(); +Census.walkCensus(census2, "census2", Census.assertAllNotLessThan(census1), new Set(["bytes"])); + +dbg.removeDebuggee(g2); +var census3 = dbg.memory.takeCensus(); +Census.walkCensus(census3, "census3", Census.assertAllEqual(census1), new Set(["bytes"])); + +dbg.removeDebuggee(g1); +var census4 = dbg.memory.takeCensus(); +Census.walkCensus(census4, "census4", Census.assertAllEqual(census0)); diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-04.js b/js/src/jit-test/tests/debug/Memory-takeCensus-04.js new file mode 100644 index 000000000..daef676dd --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-04.js @@ -0,0 +1,26 @@ +// Test that Debugger.Memory.prototype.takeCensus finds GC roots that are on the +// stack. + +var g = newGlobal(); +var dbg = new Debugger(g); + +g.eval(` + function withAllocationMarkerOnStack(f) { + (function () { + var onStack = allocationMarker(); + f(); + }()) + } +`); + +assertEq("AllocationMarker" in dbg.memory.takeCensus().objects, false, + "There shouldn't exist any allocation markers in the census."); + +var allocationMarkerCount; +g.withAllocationMarkerOnStack(() => { + allocationMarkerCount = dbg.memory.takeCensus().objects.AllocationMarker.count; +}); + +assertEq(allocationMarkerCount, 1, + "Should have one allocation marker in the census, because there " + + "was one on the stack."); diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-05.js b/js/src/jit-test/tests/debug/Memory-takeCensus-05.js new file mode 100644 index 000000000..e83425ad0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-05.js @@ -0,0 +1,14 @@ +// Test that Debugger.Memory.prototype.takeCensus finds cross compartment +// wrapper GC roots. + +var g = newGlobal(); +var dbg = new Debugger(g); + +assertEq("AllocationMarker" in dbg.memory.takeCensus().objects, false, + "There shouldn't exist any allocation markers in the census."); + +this.ccw = g.allocationMarker(); + +assertEq(dbg.memory.takeCensus().objects.AllocationMarker.count, 1, + "Should have one allocation marker in the census, because there " + + "is one cross-compartment wrapper referring to it."); diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-06.js b/js/src/jit-test/tests/debug/Memory-takeCensus-06.js new file mode 100644 index 000000000..542d38174 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-06.js @@ -0,0 +1,117 @@ +// Check Debugger.Memory.prototype.takeCensus handling of 'breakdown' argument. + +load(libdir + 'match.js'); +var Pattern = Match.Pattern; + +var g = newGlobal(); +var dbg = new Debugger(g); + +Pattern({ count: Pattern.NATURAL, + bytes: Pattern.NATURAL }) + .assert(dbg.memory.takeCensus({ breakdown: { by: 'count' } })); + +let census = dbg.memory.takeCensus({ breakdown: { by: 'count', count: false, bytes: false } }); +assertEq('count' in census, false); +assertEq('bytes' in census, false); + +census = dbg.memory.takeCensus({ breakdown: { by: 'count', count: true, bytes: false } }); +assertEq('count' in census, true); +assertEq('bytes' in census, false); + +census = dbg.memory.takeCensus({ breakdown: { by: 'count', count: false, bytes: true } }); +assertEq('count' in census, false); +assertEq('bytes' in census, true); + +census = dbg.memory.takeCensus({ breakdown: { by: 'count', count: true, bytes: true } }); +assertEq('count' in census, true); +assertEq('bytes' in census, true); + + +// Pattern doesn't mind objects with extra properties, so we'll restrict this +// list to the object classes we're pretty sure are going to stick around for +// the forseeable future. +Pattern({ + Function: { count: Pattern.NATURAL }, + Object: { count: Pattern.NATURAL }, + Debugger: { count: Pattern.NATURAL }, + global: { count: Pattern.NATURAL }, + + // The below are all Debugger prototype objects. + Source: { count: Pattern.NATURAL }, + Environment: { count: Pattern.NATURAL }, + Script: { count: Pattern.NATURAL }, + Memory: { count: Pattern.NATURAL }, + Frame: { count: Pattern.NATURAL } + }) + .assert(dbg.memory.takeCensus({ breakdown: { by: 'objectClass' } })); + +Pattern({ + objects: { count: Pattern.NATURAL }, + scripts: { count: Pattern.NATURAL }, + strings: { count: Pattern.NATURAL }, + other: { count: Pattern.NATURAL } + }) + .assert(dbg.memory.takeCensus({ breakdown: { by: 'coarseType' } })); + +// As for { by: 'objectClass' }, restrict our pattern to the types +// we predict will stick around for a long time. +Pattern({ + JSString: { count: Pattern.NATURAL }, + 'js::Shape': { count: Pattern.NATURAL }, + JSObject: { count: Pattern.NATURAL }, + JSScript: { count: Pattern.NATURAL } + }) + .assert(dbg.memory.takeCensus({ breakdown: { by: 'internalType' } })); + + +// Nested breakdowns. + +let coarse_type_pattern = { + objects: { count: Pattern.NATURAL }, + scripts: { count: Pattern.NATURAL }, + strings: { count: Pattern.NATURAL }, + other: { count: Pattern.NATURAL } +}; + +Pattern({ + JSString: coarse_type_pattern, + 'js::Shape': coarse_type_pattern, + JSObject: coarse_type_pattern, + JSScript: coarse_type_pattern, + }) + .assert(dbg.memory.takeCensus({ + breakdown: { by: 'internalType', + then: { by: 'coarseType' } + } + })); + +Pattern({ + Function: { count: Pattern.NATURAL }, + Object: { count: Pattern.NATURAL }, + Debugger: { count: Pattern.NATURAL }, + global: { count: Pattern.NATURAL }, + other: coarse_type_pattern + }) + .assert(dbg.memory.takeCensus({ + breakdown: { + by: 'objectClass', + then: { by: 'count' }, + other: { by: 'coarseType' } + } + })); + +Pattern({ + objects: { count: Pattern.NATURAL, label: "object" }, + scripts: { count: Pattern.NATURAL, label: "scripts" }, + strings: { count: Pattern.NATURAL, label: "strings" }, + other: { count: Pattern.NATURAL, label: "other" } + }) + .assert(dbg.memory.takeCensus({ + breakdown: { + by: 'coarseType', + objects: { by: 'count', label: 'object' }, + scripts: { by: 'count', label: 'scripts' }, + strings: { by: 'count', label: 'strings' }, + other: { by: 'count', label: 'other' } + } + })); diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-07.js b/js/src/jit-test/tests/debug/Memory-takeCensus-07.js new file mode 100644 index 000000000..6dae65eac --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-07.js @@ -0,0 +1,75 @@ +// Debugger.Memory.prototype.takeCensus breakdown: check error handling on +// property gets. + +load(libdir + 'asserts.js'); + +var g = newGlobal(); +var dbg = new Debugger(g); + +assertThrowsValue(() => { + dbg.memory.takeCensus({ + breakdown: { get by() { throw "ಠ_ಠ" } } + }); +}, "ಠ_ಠ"); + + + +assertThrowsValue(() => { + dbg.memory.takeCensus({ + breakdown: { by: 'count', get count() { throw "ಠ_ಠ" } } + }); +}, "ಠ_ಠ"); + +assertThrowsValue(() => { + dbg.memory.takeCensus({ + breakdown: { by: 'count', get bytes() { throw "ಠ_ಠ" } } + }); +}, "ಠ_ಠ"); + + + +assertThrowsValue(() => { + dbg.memory.takeCensus({ + breakdown: { by: 'objectClass', get then() { throw "ಠ_ಠ" } } + }); +}, "ಠ_ಠ"); + +assertThrowsValue(() => { + dbg.memory.takeCensus({ + breakdown: { by: 'objectClass', get other() { throw "ಠ_ಠ" } } + }); +}, "ಠ_ಠ"); + + + +assertThrowsValue(() => { + dbg.memory.takeCensus({ + breakdown: { by: 'coarseType', get objects() { throw "ಠ_ಠ" } } + }); +}, "ಠ_ಠ"); + +assertThrowsValue(() => { + dbg.memory.takeCensus({ + breakdown: { by: 'coarseType', get scripts() { throw "ಠ_ಠ" } } + }); +}, "ಠ_ಠ"); + +assertThrowsValue(() => { + dbg.memory.takeCensus({ + breakdown: { by: 'coarseType', get strings() { throw "ಠ_ಠ" } } + }); +}, "ಠ_ಠ"); + +assertThrowsValue(() => { + dbg.memory.takeCensus({ + breakdown: { by: 'coarseType', get other() { throw "ಠ_ಠ" } } + }); +}, "ಠ_ಠ"); + + + +assertThrowsValue(() => { + dbg.memory.takeCensus({ + breakdown: { by: 'internalType', get then() { throw "ಠ_ಠ" } } + }); +}, "ಠ_ಠ"); diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-08.js b/js/src/jit-test/tests/debug/Memory-takeCensus-08.js new file mode 100644 index 000000000..637f0d059 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-08.js @@ -0,0 +1,73 @@ +// Debugger.Memory.prototype.takeCensus: test by: 'count' breakdown + +let g = newGlobal(); +let dbg = new Debugger(g); + +g.eval(` + var stuff = []; + function add(n, c) { + for (let i = 0; i < n; i++) + stuff.push(c()); + } + + let count = 0; + + function obj() { return { count: count++ }; } + obj.factor = 1; + + // This creates a closure (a function JSObject) that has captured + // a Call object. So each call creates two items. + function fun() { let v = count; return () => { return v; } } + fun.factor = 2; + + function str() { return 'perambulator' + count++; } + str.factor = 1; + + // Eval a fresh text each time, allocating: + // - a fresh ScriptSourceObject + // - a new JSScripts, not an eval cache hits + // - a fresh prototype object + // - a fresh Call object, since the eval makes 'ev' heavyweight + // - the new function itself + function ev() { + return eval(\`(function () { return \${ count++ } })\`); + } + ev.factor = 5; + + // A new object (1) with a new shape (2) with a new atom (3) + function shape() { return { [ 'theobroma' + count++ ]: count }; } + shape.factor = 3; + `); + +let baseline = 0; +function countIncreasedByAtLeast(n) { + let oldBaseline = baseline; + + // Since a census counts only reachable objects, one might assume that calling + // GC here would have no effect on the census results. But GC also throws away + // JIT code and any objects it might be holding (template objects, say); + // takeCensus reaches those. Shake everything loose that we can, to make the + // census approximate reachability a bit more closely, and make our results a + // bit more predictable. + gc(g, 'shrinking'); + + baseline = dbg.memory.takeCensus({ breakdown: { by: 'count' } }).count; + return baseline >= oldBaseline + n; +} + +countIncreasedByAtLeast(0); + +g.add(100, g.obj); +assertEq(countIncreasedByAtLeast(g.obj.factor * 100), true); + +g.add(100, g.fun); +assertEq(countIncreasedByAtLeast(g.fun.factor * 100), true); + +g.add(100, g.str); +assertEq(countIncreasedByAtLeast(g.str.factor * 100), true); + +g.add(100, g.ev); +assertEq(countIncreasedByAtLeast(g.ev.factor * 100), true); + +g.add(100, g.shape); +assertEq(countIncreasedByAtLeast(g.shape.factor * 100), true); diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-09.js b/js/src/jit-test/tests/debug/Memory-takeCensus-09.js new file mode 100644 index 000000000..09b28da34 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-09.js @@ -0,0 +1,74 @@ +// Debugger.Memory.prototype.takeCensus: by: allocationStack breakdown + +var g = newGlobal(); +var dbg = new Debugger(g); + +g.evaluate(` + var log = []; + function f() { log.push(allocationMarker()); } + function g() { f(); } + function h() { f(); } + `, + { fileName: "Rockford", lineNumber: 1000 }); + +// Create one allocationMarker with tracking turned off, +// so it will have no associated stack. +g.f(); + +dbg.memory.allocationSamplingProbability = 1; +dbg.memory.trackingAllocationSites = true; + +for ([f, n] of [[g.f, 20], [g.g, 10], [g.h, 5]]) + for (let i = 0; i < n; i++) + f(); // all allocations of allocationMarker occur with this line as the + // oldest stack frame. + +let census = dbg.memory.takeCensus({ breakdown: { by: 'objectClass', + then: { by: 'allocationStack', + then: { by: 'count', + label: 'haz stack' + }, + noStack: { by: 'count', + label: 'no haz stack' + } + } + } + }); + +let map = census.AllocationMarker; +assertEq(map instanceof Map, true); + +// Gather the stacks we are expecting to appear as keys, and +// check that there are no unexpected keys. +let stacks = { }; + +map.forEach((v, k) => { + if (k === 'noStack') { + // No need to save this key. + } else if (k.functionDisplayName === 'f' && + k.parent.functionDisplayName === null) { + stacks.f = k; + } else if (k.functionDisplayName === 'f' && + k.parent.functionDisplayName === 'g' && + k.parent.parent.functionDisplayName === null) { + stacks.fg = k; + } else if (k.functionDisplayName === 'f' && + k.parent.functionDisplayName === 'h' && + k.parent.parent.functionDisplayName === null) { + stacks.fh = k; + } else { + assertEq(true, false); + } +}); + +assertEq(map.get('noStack').label, 'no haz stack'); +assertEq(map.get('noStack').count, 1); + +assertEq(map.get(stacks.f).label, 'haz stack'); +assertEq(map.get(stacks.f).count, 20); + +assertEq(map.get(stacks.fg).label, 'haz stack'); +assertEq(map.get(stacks.fg).count, 10); + +assertEq(map.get(stacks.fh).label, 'haz stack'); +assertEq(map.get(stacks.fh).count, 5); diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-10.js b/js/src/jit-test/tests/debug/Memory-takeCensus-10.js new file mode 100644 index 000000000..e7b82379c --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-10.js @@ -0,0 +1,57 @@ +// Check byte counts produced by takeCensus. + +let g = newGlobal(); +let dbg = new Debugger(g); + +let sizeOfAM = byteSize(allocationMarker()); + +// Allocate a single allocation marker, and check that we can find it. +g.eval('var hold = allocationMarker();'); +let census = dbg.memory.takeCensus({ breakdown: { by: 'objectClass' } }); +assertEq(census.AllocationMarker.count, 1); +assertEq(census.AllocationMarker.bytes, sizeOfAM); + +g.evaluate(` + var objs = []; + function fnerd() { + objs.push(allocationMarker()); + for (let i = 0; i < 10; i++) + objs.push(allocationMarker()); + } + `, + { fileName: 'J. Edgar Hoover', lineNumber: 2000 }); + +dbg.memory.allocationSamplingProbability = 1; +dbg.memory.trackingAllocationSites = true; + +g.hold = null; +g.fnerd(); + +census = dbg.memory.takeCensus({ + breakdown: { by: 'objectClass', + then: { by: 'allocationStack' } + } +}); + +let seen = 0; +census.AllocationMarker.forEach((v, k) => { + assertEq(k.functionDisplayName, 'fnerd'); + assertEq(k.source, 'J. Edgar Hoover'); + switch (k.line) { + case 2003: + assertEq(v.count, 1); + assertEq(v.bytes, sizeOfAM); + seen++; + break; + + case 2005: + assertEq(v.count, 10); + assertEq(v.bytes, 10 * sizeOfAM); + seen++; + break; + + default: assertEq(true, false); + } +}); + +assertEq(seen, 2); diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-11.js b/js/src/jit-test/tests/debug/Memory-takeCensus-11.js new file mode 100644 index 000000000..c9c65b003 --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-11.js @@ -0,0 +1,45 @@ +// Check byte counts produced by takeCensus. + +const g = newGlobal(); +g.eval("setLazyParsingDisabled(true)"); +g.eval("setJitCompilerOption('ion.warmup.trigger', 1000)"); + +const dbg = new Debugger(g); + +g.evaluate("function one() {}", { fileName: "one.js" }); +g.evaluate(`function two1() {} + function two2() {}`, + { fileName: "two.js" }); +g.evaluate(`function three1() {} + function three2() {} + function three3() {}`, + { fileName: "three.js" }); + +const report = dbg.memory.takeCensus({ + breakdown: { + by: "coarseType", + scripts: { + by: "filename", + then: { by: "count", count: true, bytes: false }, + noFilename: { + by: "internalType", + then: { by: "count", count: true, bytes: false } + } + }, + + // Not really interested in these, but they're here for completeness. + objects: { by: "count", count: true, byte: false }, + strings: { by: "count", count: true, byte: false }, + other: { by: "count", count: true, byte: false }, + } +}); + +print(JSON.stringify(report, null, 4)); + +assertEq(report.scripts["one.js"].count, 1); +assertEq(report.scripts["two.js"].count, 2); +assertEq(report.scripts["three.js"].count, 3); + +const noFilename = report.scripts.noFilename; +assertEq(!!noFilename, true); +assertEq(typeof noFilename, "object"); diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-12.js b/js/src/jit-test/tests/debug/Memory-takeCensus-12.js new file mode 100644 index 000000000..49e57849b --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-12.js @@ -0,0 +1,61 @@ +// Sanity test that we can accumulate matching individuals in a bucket. + +var g = newGlobal(); +var dbg = new Debugger(g); + +var bucket = { by: "bucket" }; +var count = { by: "count", count: true, bytes: false }; + +var all = dbg.memory.takeCensus({ breakdown: bucket }); +var allCount = dbg.memory.takeCensus({ breakdown: count }).count; + +var coarse = dbg.memory.takeCensus({ + breakdown: { + by: "coarseType", + objects: bucket, + strings: bucket, + scripts: bucket, + other: bucket + } +}); +var coarseCount = dbg.memory.takeCensus({ + breakdown: { + by: "coarseType", + objects: count, + strings: count, + scripts: count, + other: count + } +}); + +assertEq(all.length > 0, true); +assertEq(all.length, allCount); + +assertEq(coarse.objects.length > 0, true); +assertEq(coarseCount.objects.count, coarse.objects.length); + +assertEq(coarse.strings.length > 0, true); +assertEq(coarseCount.strings.count, coarse.strings.length); + +assertEq(coarse.scripts.length > 0, true); +assertEq(coarseCount.scripts.count, coarse.scripts.length); + +assertEq(coarse.other.length > 0, true); +assertEq(coarseCount.other.count, coarse.other.length); + +assertEq(all.length >= coarse.objects.length, true); +assertEq(all.length >= coarse.strings.length, true); +assertEq(all.length >= coarse.scripts.length, true); +assertEq(all.length >= coarse.other.length, true); + +function assertIsIdentifier(id) { + assertEq(id, Math.floor(id)); + assertEq(id > 0, true); + assertEq(id <= Math.pow(2, 48), true); +} + +all.forEach(assertIsIdentifier); +coarse.objects.forEach(assertIsIdentifier); +coarse.strings.forEach(assertIsIdentifier); +coarse.scripts.forEach(assertIsIdentifier); +coarse.other.forEach(assertIsIdentifier); diff --git a/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-01.js b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-01.js new file mode 100644 index 000000000..4175dfeda --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-01.js @@ -0,0 +1,37 @@ +// Test that we can track allocation sites by setting +// Debugger.Memory.prototype.trackingAllocationSites to true and then get the +// allocation site via Debugger.Object.prototype.allocationSite. + +const root = newGlobal(); + +const dbg = new Debugger(); +const wrappedRoot = dbg.addDebuggee(root); + +assertEq(dbg.memory.trackingAllocationSites, false); +dbg.memory.trackingAllocationSites = true; +assertEq(dbg.memory.trackingAllocationSites, true); + +root.eval("(" + function immediate() { + this.tests = [ + { name: "object literal", object: ({}), line: Error().lineNumber }, + { name: "array literal", object: [], line: Error().lineNumber }, + { name: "regexp literal", object: /(two|2)\s*problems/, line: Error().lineNumber }, + { name: "new constructor", object: new function Ctor(){}, line: Error().lineNumber }, + { name: "new Object", object: new Object(), line: Error().lineNumber }, + { name: "new Array", object: new Array(), line: Error().lineNumber }, + { name: "new Date", object: new Date(), line: Error().lineNumber } + ]; +} + "());"); + +dbg.memory.trackingAllocationSites = false; +assertEq(dbg.memory.trackingAllocationSites, false); + +for (let { name, object, line } of root.tests) { + print("Entering test: " + name); + + let wrappedObject = wrappedRoot.makeDebuggeeValue(object); + let allocationSite = wrappedObject.allocationSite; + print("Allocation site: " + allocationSite); + + assertEq(allocationSite.line, line); +} diff --git a/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-02.js b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-02.js new file mode 100644 index 000000000..fff4666cc --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-02.js @@ -0,0 +1,19 @@ +// Test that we don't get allocation sites when nobody has asked for them. + +const root = newGlobal(); + +const dbg = new Debugger(); +const wrappedRoot = dbg.addDebuggee(root); + +dbg.memory.trackingAllocationSites = true; +root.eval("this.obj = {};"); +dbg.memory.trackingAllocationSites = false; +root.eval("this.obj2 = {};"); + +let wrappedObj = wrappedRoot.makeDebuggeeValue(root.obj); +let allocationSite = wrappedObj.allocationSite; +assertEq(allocationSite != null && typeof allocationSite == "object", true); + +let wrappedObj2 = wrappedRoot.makeDebuggeeValue(root.obj2); +let allocationSite2 = wrappedObj2.allocationSite; +assertEq(allocationSite2, null); diff --git a/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-03.js b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-03.js new file mode 100644 index 000000000..4770537cc --- /dev/null +++ b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-03.js @@ -0,0 +1,105 @@ +// Test that multiple Debuggers behave reasonably. + +load(libdir + "asserts.js"); + +let dbg1, dbg2, root1, root2; + +function isTrackingAllocations(global, dbgObj) { + const site = dbgObj.makeDebuggeeValue(global.eval("({})")).allocationSite; + if (site) { + assertEq(typeof site, "object"); + } + return !!site; +} + +function test(name, fn) { + print(); + print(name); + + // Reset state. + root1 = newGlobal(); + root2 = newGlobal(); + dbg1 = new Debugger; + dbg2 = new Debugger; + + // Run the test. + fn(); + + print(" OK"); +} + +test("Can track allocations even if a different debugger is already tracking " + + "them.", + () => { + let d1r1 = dbg1.addDebuggee(root1); + let d2r1 = dbg2.addDebuggee(root1); + dbg1.memory.trackingAllocationSites = true; + dbg2.memory.trackingAllocationSites = true; + assertEq(isTrackingAllocations(root1, d1r1), true); + assertEq(isTrackingAllocations(root1, d2r1), true); + }); + +test("Removing root1 as a debuggee from all debuggers should disable the " + + "allocation hook.", + () => { + dbg1.memory.trackingAllocationSites = true; + let d1r1 = dbg1.addDebuggee(root1); + dbg1.removeAllDebuggees(); + assertEq(isTrackingAllocations(root1, d1r1), false); + }); + +test("Adding a new debuggee to a debugger that is tracking allocations should " + + "enable the hook for the new debuggee.", + () => { + dbg1.memory.trackingAllocationSites = true; + let d1r1 = dbg1.addDebuggee(root1); + assertEq(isTrackingAllocations(root1, d1r1), true); + }); + +test("Setting trackingAllocationSites to true should throw if the debugger " + + "cannot install the allocation hooks for *every* debuggee.", + () => { + let d1r1 = dbg1.addDebuggee(root1); + let d1r2 = dbg1.addDebuggee(root2); + + // Can't install allocation hooks for root2 with this set. + root2.enableShellAllocationMetadataBuilder(); + + assertThrowsInstanceOf(() => dbg1.memory.trackingAllocationSites = true, + Error); + + // And after it throws, its trackingAllocationSites accessor should reflect that + // allocation site tracking is still disabled in that Debugger. + assertEq(dbg1.memory.trackingAllocationSites, false); + assertEq(isTrackingAllocations(root1, d1r1), false); + assertEq(isTrackingAllocations(root2, d1r2), false); + }); + +test("A Debugger isn't tracking allocation sites when disabled.", + () => { + dbg1.memory.trackingAllocationSites = true; + let d1r1 = dbg1.addDebuggee(root1); + + assertEq(isTrackingAllocations(root1, d1r1), true); + dbg1.enabled = false; + assertEq(isTrackingAllocations(root1, d1r1), false); + }); + +test("Re-enabling throws an error if we can't reinstall allocations tracking " + + "for all debuggees.", + () => { + dbg1.enabled = false + dbg1.memory.trackingAllocationSites = true; + let d1r1 = dbg1.addDebuggee(root1); + let d1r2 = dbg1.addDebuggee(root2); + + // Can't install allocation hooks for root2 with this set. + root2.enableShellAllocationMetadataBuilder(); + + assertThrowsInstanceOf(() => dbg1.enabled = true, + Error); + + assertEq(dbg1.enabled, false); + assertEq(isTrackingAllocations(root1, d1r1), false); + assertEq(isTrackingAllocations(root2, d1r2), false); + }); diff --git a/js/src/jit-test/tests/debug/Object-01.js b/js/src/jit-test/tests/debug/Object-01.js new file mode 100644 index 000000000..c897ee713 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-01.js @@ -0,0 +1,17 @@ +// Debugger.Object basics + +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.arguments[0], frame.callee); + assertEq(Object.getPrototypeOf(frame.arguments[0]), Debugger.Object.prototype); + assertEq(frame.arguments[0] instanceof Debugger.Object, true); + assertEq(frame.arguments[0] !== frame.arguments[1], true); + assertEq(Object.getPrototypeOf(frame.arguments[1]), Debugger.Object.prototype); + assertEq(frame.arguments[1] instanceof Debugger.Object, true); + hits++; +}; + +g.eval("var obj = {}; function f(a, b) { debugger; } f(f, obj);"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Object-02.js b/js/src/jit-test/tests/debug/Object-02.js new file mode 100644 index 000000000..57e862084 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-02.js @@ -0,0 +1,13 @@ +// Debugger.Object referents can be transparent wrappers of objects in the debugger compartment. + +var g = newGlobal(); +g.f = function (a, b) { return a + "/" + b; }; +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var f = frame.eval("f").return; + assertEq(f.call(null, "a", "b").return, "a/b"); + hits++; +}; +g.eval("debugger;"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Object-apply-01.js b/js/src/jit-test/tests/debug/Object-apply-01.js new file mode 100644 index 000000000..954e80b66 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-apply-01.js @@ -0,0 +1,59 @@ +// tests calling script functions via Debugger.Object.prototype.apply/call + +load(libdir + "asserts.js"); + +var g = newGlobal(); +g.eval("function f() { debugger; }"); +var dbg = new Debugger(g); + +var hits = 0; +function test(usingApply) { + dbg.onDebuggerStatement = function (frame) { + var fn = frame.arguments[0]; + var cv = usingApply ? fn.apply(null, [9, 16]) : fn.call(null, 9, 16); + assertEq(Object.keys(cv).join(","), "return"); + assertEq(Object.getPrototypeOf(cv), Object.prototype); + assertEq(cv.return, 25); + + cv = usingApply ? fn.apply(null, ["hello ", "world"]) : fn.call(null, "hello ", "world"); + assertEq(Object.keys(cv).join(","), "return"); + assertEq(cv.return, "hello world"); + + // Handle more or less arguments. + assertEq((usingApply ? fn.apply(null, [1, 5, 100]) : fn.call(null, 1, 5, 100)).return, 6); + assertEq((usingApply ? fn.apply(null, []) : fn.call(null)).return, NaN); + assertEq((usingApply ? fn.apply() : fn.call()).return, NaN); + + // Throw if a this-value or argument is an object but not a Debugger.Object. + assertThrowsInstanceOf(function () { usingApply ? fn.apply({}, []) : fn.call({}); }, + TypeError); + assertThrowsInstanceOf(function () { usingApply ? fn.apply(null, [{}]) : fn.call(null, {}); }, + TypeError); + hits++; + }; + g.eval("f(function (a, b) { return a + b; });"); + + // The callee receives the right arguments even if more arguments are provided + // than the callee's .length. + dbg.onDebuggerStatement = function (frame) { + assertEq((usingApply ? frame.arguments[0].apply(null, ['one', 'two']) + : frame.arguments[0].call(null, 'one', 'two')).return, + 2); + hits++; + }; + g.eval("f(function () { return arguments.length; });"); + + // Exceptions are reported as {throw:} completion values. + dbg.onDebuggerStatement = function (frame) { + var lose = frame.arguments[0]; + var cv = usingApply ? lose.apply(null, []) : lose.call(null); + assertEq(Object.keys(cv).join(","), "throw"); + assertEq(cv.throw, frame.callee); + hits++; + }; + g.eval("f(function lose() { throw f; });"); +} + +test(true); +test(false); +assertEq(hits, 6); diff --git a/js/src/jit-test/tests/debug/Object-apply-02.js b/js/src/jit-test/tests/debug/Object-apply-02.js new file mode 100644 index 000000000..938c75a52 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-apply-02.js @@ -0,0 +1,58 @@ +// tests calling native functions via Debugger.Object.prototype.apply/call + +load(libdir + "asserts.js"); + +var g = newGlobal(); +g.eval("function f() { debugger; }"); +var dbg = new Debugger(g); + +function test(usingApply) { + dbg.onDebuggerStatement = function (frame) { + var max = frame.arguments[0]; + var cv = usingApply ? max.apply(null, [9, 16]) : max.call(null, 9, 16); + assertEq(cv.return, 16); + + cv = usingApply ? max.apply() : max.call(); + assertEq(cv.return, -1/0); + + cv = usingApply ? max.apply(null, [2, 5, 3, 8, 1, 9, 4, 6, 7]) + : max.call(null, 2, 5, 3, 8, 1, 9, 4, 6, 7); + assertEq(cv.return, 9); + + // second argument to apply must be an array + assertThrowsInstanceOf(function () { max.apply(null, 12); }, TypeError); + }; + g.eval("f(Math.max);"); + + dbg.onDebuggerStatement = function (frame) { + var push = frame.arguments[0]; + var arr = frame.arguments[1]; + var cv; + + cv = usingApply ? push.apply(arr, [0, 1, 2]) : push.call(arr, 0, 1, 2); + assertEq(cv.return, 3); + + cv = usingApply ? push.apply(arr, [arr]) : push.call(arr, arr); + assertEq(cv.return, 4); + + cv = usingApply ? push.apply(arr) : push.call(arr); + assertEq(cv.return, 4); + + // You can apply Array.prototype.push to a string; it does ToObject on + // it. But as the length property on String objects is non-writable, + // attempting to increase the length will throw a TypeError. + cv = usingApply + ? push.apply("hello", ["world"]) + : push.call("hello", "world"); + assertEq("throw" in cv, true); + var ex = cv.throw; + assertEq(frame.evalWithBindings("ex instanceof TypeError", { ex: ex }).return, true); + }; + g.eval("var a = []; f(Array.prototype.push, a);"); + assertEq(g.a.length, 4); + assertEq(g.a.slice(0, 3).join(","), "0,1,2"); + assertEq(g.a[3], g.a); +} + +test(true); +test(false); diff --git a/js/src/jit-test/tests/debug/Object-apply-03.js b/js/src/jit-test/tests/debug/Object-apply-03.js new file mode 100644 index 000000000..3f29809d6 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-apply-03.js @@ -0,0 +1,21 @@ +// reentering the debugger several times via onDebuggerStatement and apply/call on a single stack + +var g = newGlobal(); +var dbg = Debugger(g); + +function test(usingApply) { + dbg.onDebuggerStatement = function (frame) { + var n = frame.arguments[0]; + if (n > 1) { + var result = usingApply ? frame.callee.apply(null, [n - 1]) + : frame.callee.call(null, n - 1); + result.return *= n; + return result; + } + }; + g.eval("function fac(n) { debugger; return 1; }"); + assertEq(g.fac(5), 5 * 4 * 3 * 2 * 1); +} + +test(true); +test(false); diff --git a/js/src/jit-test/tests/debug/Object-apply-04.js b/js/src/jit-test/tests/debug/Object-apply-04.js new file mode 100644 index 000000000..dd327d87e --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-apply-04.js @@ -0,0 +1,15 @@ +// Debugger.Object.prototype.apply/call works with function proxies + +var g = newGlobal(); +g.eval("function f() { debugger; }"); +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var proxy = frame.arguments[0]; + assertEq(proxy.name, undefined); + assertEq(proxy.apply(null, [33]).return, 34); + assertEq(proxy.call(null, 33).return, 34); + hits++; +}; +g.eval("f(new Proxy(function (arg) { return arg + 1; }, {}));"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Object-asEnvironment-01.js b/js/src/jit-test/tests/debug/Object-asEnvironment-01.js new file mode 100644 index 000000000..1f5e3be42 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-asEnvironment-01.js @@ -0,0 +1,15 @@ +// Tests D.O.asEnvironment() returning the global lexical scope. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +g.evaluate(` + var x = 42; + let y = "foo" +`); + +var globalLexical = gw.asEnvironment(); +assertEq(globalLexical.names().length, 1); +assertEq(globalLexical.getVariable("y"), "foo"); +assertEq(globalLexical.parent.getVariable("x"), 42); diff --git a/js/src/jit-test/tests/debug/Object-boundTargetFunction-01.js b/js/src/jit-test/tests/debug/Object-boundTargetFunction-01.js new file mode 100644 index 000000000..033d6b07e --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-boundTargetFunction-01.js @@ -0,0 +1,26 @@ +// Smoke tests for bound function things. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var arrw = gw.executeInGlobal("var arr = []; arr;").return; +var pushw = gw.executeInGlobal("var push = arr.push.bind(arr); push;").return; +assertEq(pushw.isBoundFunction, true); +assertEq(pushw.boundThis, arrw); +assertEq(pushw.boundArguments.length, 0); + +var arr2 = gw.executeInGlobal("var arr2 = []; arr2").return; +assertEq(pushw.call(arr2, "tuesday").return, 1); +g.eval("assertEq(arr.length, 1);"); +g.eval("assertEq(arr[0], 'tuesday');"); +g.eval("assertEq(arr2.length, 0);"); + +g.eval("push = arr.push.bind(arr, 1, 'seven', {x: 'q'});"); +pushw = gw.getOwnPropertyDescriptor("push").value; +assertEq(pushw.isBoundFunction, true); +var args = pushw.boundArguments; +assertEq(args.length, 3); +assertEq(args[0], 1); +assertEq(args[1], 'seven'); +assertEq(args[2] instanceof Debugger.Object, true); +assertEq(args[2].getOwnPropertyDescriptor("x").value, "q"); diff --git a/js/src/jit-test/tests/debug/Object-boundTargetFunction-02.js b/js/src/jit-test/tests/debug/Object-boundTargetFunction-02.js new file mode 100644 index 000000000..d5dadf9b8 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-boundTargetFunction-02.js @@ -0,0 +1,25 @@ +// Test that bound function accessors work on: +// - an ordinary non-bound function; +// - a native function; +// - and an object that isn't a function at all. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var fw = gw.executeInGlobal("function f() {}; f").return; +assertEq(fw.isBoundFunction, false); +assertEq(fw.boundThis, undefined); +assertEq(fw.boundArguments, undefined); +assertEq(fw.boundTargetFunction, undefined); + +var nw = gw.executeInGlobal("var n = Math.max; n").return; +assertEq(nw.isBoundFunction, false); +assertEq(nw.boundThis, undefined); +assertEq(fw.boundArguments, undefined); +assertEq(nw.boundTargetFunction, undefined); + +var ow = gw.executeInGlobal("var o = {}; o").return; +assertEq(ow.isBoundFunction, undefined); +assertEq(ow.boundThis, undefined); +assertEq(fw.boundArguments, undefined); +assertEq(ow.boundTargetFunction, undefined); diff --git a/js/src/jit-test/tests/debug/Object-boundTargetFunction-03.js b/js/src/jit-test/tests/debug/Object-boundTargetFunction-03.js new file mode 100644 index 000000000..705302dad --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-boundTargetFunction-03.js @@ -0,0 +1,20 @@ +// Test that inspecting a bound function that was bound again does the right +// thing. + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); +var expr = "function f() { return this; }; var bf = f.bind(1, 2).bind(3, 4); bf"; +var bfw = gw.executeInGlobal(expr).return; + +assertEq(bfw.isBoundFunction, true); +assertEq(bfw.boundThis, 3); +var args = bfw.boundArguments; +assertEq(args.length, 1); +assertEq(args[0], 4); + +assertEq(bfw.boundTargetFunction.isBoundFunction, true); +assertEq(bfw.boundTargetFunction.boundThis, 1); +args = bfw.boundTargetFunction.boundArguments; +assertEq(args.length, 1); +assertEq(args[0], 2); diff --git a/js/src/jit-test/tests/debug/Object-callable.js b/js/src/jit-test/tests/debug/Object-callable.js new file mode 100644 index 000000000..50d55b428 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-callable.js @@ -0,0 +1,18 @@ +// Test Debugger.Object.prototype.callable. + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.arguments[0].callable, frame.arguments[1]); + hits++; +}; + +g.eval("function f(obj, iscallable) { debugger; }"); + +g.eval("f({}, false);"); +g.eval("f(Function.prototype, true);"); +g.eval("f(f, true);"); +g.eval("f(new Proxy({}, {}), false);"); +g.eval("f(new Proxy(f, {}), true);"); +assertEq(hits, 5); diff --git a/js/src/jit-test/tests/debug/Object-class.js b/js/src/jit-test/tests/debug/Object-class.js new file mode 100644 index 000000000..5e90c6562 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-class.js @@ -0,0 +1,26 @@ +// Basic tests for Debugger.Object.prototype.class. +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +g.eval('function f() { debugger; }'); + +dbg.onDebuggerStatement = function (frame) { + var arr = frame.arguments; + assertEq(arr[0].class, "Object"); + assertEq(arr[1].class, "Array"); + assertEq(arr[2].class, "Function"); + assertEq(arr[3].class, "Date"); + assertEq(arr[4].class, "Object"); + assertEq(arr[5].class, "Function"); + assertEq(arr[6].class, "Object"); + hits++; +}; +g.f(Object.prototype, [], eval, new Date, + new Proxy({}, {}), new Proxy(eval, {}), new Proxy(new Date, {})); +assertEq(hits, 1); + +// Debugger.Object.prototype.class should see through cross-compartment +// wrappers. +g.eval('f(Object.prototype, [], eval, new Date,\ + new Proxy({}, {}), new Proxy(f, {}), new Proxy(new Date, {}));'); +assertEq(hits, 2); diff --git a/js/src/jit-test/tests/debug/Object-defineProperties-01.js b/js/src/jit-test/tests/debug/Object-defineProperties-01.js new file mode 100644 index 000000000..ee1bce436 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperties-01.js @@ -0,0 +1,46 @@ +// Debug.Object.prototype.defineProperties. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +var descProps = ['configurable', 'enumerable', 'writable', 'value', 'get', 'set']; +function test(objexpr, descs) { + g.eval("obj = (" + objexpr + ");"); + var gobjw = gw.getOwnPropertyDescriptor("obj").value; + gobjw.defineProperties(descs); + + var indirectEval = eval; + var obj = indirectEval("(" + objexpr + ");"); + Object.defineProperties(obj, descs); + + var ids = Object.keys(descs); + for (var i = 0; i < ids.length; i++) { + var actual = gobjw.getOwnPropertyDescriptor(ids[i]); + var expected = Object.getOwnPropertyDescriptor(obj, ids[i]); + assertEq(Object.getPrototypeOf(actual), Object.prototype); + assertEq(actual.configurable, expected.configurable); + assertEq(actual.enumerable, expected.enumerable); + for (var j = 0; j < descProps; j++) { + var prop = descProps[j]; + assertEq(prop in actual, prop in expected); + assertEq(actual[prop], expected[prop]); + } + } +} + +test("{}", {}); +test("/abc/", {}); + +g.eval("var aglobal = newGlobal('same-compartment');"); +var aglobal = newGlobal('same-compartment'); +test("aglobal", {}); + +var adescs = {a: {enumerable: true, writable: true, value: 0}}; +test("{}", adescs); +test("{a: 1}", adescs); + +var arrdescs = [{value: 'a'}, {value: 'b'}, , {value: 'd'}]; +test("{}", arrdescs); +test("[]", arrdescs); +test("[0, 1, 2, 3]", arrdescs); diff --git a/js/src/jit-test/tests/debug/Object-defineProperties-02.js b/js/src/jit-test/tests/debug/Object-defineProperties-02.js new file mode 100644 index 000000000..2d930449c --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperties-02.js @@ -0,0 +1,33 @@ +// Exceptions thrown by obj.defineProperties are copied into the debugger compartment. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +function test(objexpr, descs) { + var exca, excb; + + g.eval("obj = (" + objexpr + ");"); + var gobjw = gw.getOwnPropertyDescriptor("obj").value; + try { + gobjw.defineProperties(descs); + } catch (exc) { + exca = exc; + } + + var indirectEval = eval; + var obj = indirectEval("(" + objexpr + ");"); + try { + Object.defineProperties(obj, descs); + } catch (exc) { + excb = exc; + } + + assertEq(Object.getPrototypeOf(exca), Object.getPrototypeOf(excb)); + assertEq(exca.message, excb.message); + assertEq(typeof exca.fileName, "string"); + assertEq(typeof exca.stack, "string"); +} + +test("Object.create(null, {p: {value: 1}})", {p: {value: 2}}); +test("({})", {x: {get: 'bad'}}); diff --git a/js/src/jit-test/tests/debug/Object-defineProperties-03.js b/js/src/jit-test/tests/debug/Object-defineProperties-03.js new file mode 100644 index 000000000..7e7773281 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperties-03.js @@ -0,0 +1,20 @@ +// obj.defineProperties can define accessor properties. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +g.value = undefined; +g.eval("function gf() { return 12; }\n" + + "function sf(v) { value = v; }\n"); +var gfw = gw.getOwnPropertyDescriptor("gf").value; +var sfw = gw.getOwnPropertyDescriptor("sf").value; +gw.defineProperties({x: {configurable: true, get: gfw, set: sfw}}); +assertEq(g.x, 12); +g.x = 'ok'; +assertEq(g.value, 'ok'); + +var desc = g.Object.getOwnPropertyDescriptor(g, "x"); +assertEq(desc.configurable, true); +assertEq(desc.enumerable, false); +assertEq(desc.get, g.gf); +assertEq(desc.set, g.sf); diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-01.js b/js/src/jit-test/tests/debug/Object-defineProperty-01.js new file mode 100644 index 000000000..21812af08 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperty-01.js @@ -0,0 +1,12 @@ +// obj.defineProperty can define simple data properties. + +var g = newGlobal(); +var dbg = new Debugger; +var gobj = dbg.addDebuggee(g); +gobj.defineProperty("x", {configurable: true, enumerable: true, writable: true, value: 'ok'}); +assertEq(g.x, 'ok'); + +var desc = g.Object.getOwnPropertyDescriptor(g, "x"); +assertEq(desc.configurable, true); +assertEq(desc.enumerable, true); +assertEq(desc.writable, true); diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-02.js b/js/src/jit-test/tests/debug/Object-defineProperty-02.js new file mode 100644 index 000000000..0b1c43bd1 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperty-02.js @@ -0,0 +1,10 @@ +// obj.defineProperty can define a data property with object value. + +var g = newGlobal(); +g.eval("var a = {};"); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var desc = gw.getOwnPropertyDescriptor("a"); +assertEq(desc.value instanceof Debugger.Object, true); +gw.defineProperty("b", desc); +assertEq(g.a, g.b); diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-03.js b/js/src/jit-test/tests/debug/Object-defineProperty-03.js new file mode 100644 index 000000000..82add1791 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperty-03.js @@ -0,0 +1,21 @@ +// defineProperty can set array elements + +var g = newGlobal(); +g.a = g.Array(0, 1, 2); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var aw = gw.getOwnPropertyDescriptor("a").value; + +aw.defineProperty(0, {value: 'ok0'}); // by number +assertEq(g.a[0], 'ok0'); +var desc = g.Object.getOwnPropertyDescriptor(g.a, "0"); +assertEq(desc.configurable, true); +assertEq(desc.enumerable, true); +assertEq(desc.writable, true); + +aw.defineProperty("1", {value: 'ok1'}); // by string +assertEq(g.a[1], 'ok1'); +desc = g.Object.getOwnPropertyDescriptor(g.a, "1"); +assertEq(desc.configurable, true); +assertEq(desc.enumerable, true); +assertEq(desc.writable, true); diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-04.js b/js/src/jit-test/tests/debug/Object-defineProperty-04.js new file mode 100644 index 000000000..e109e748e --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperty-04.js @@ -0,0 +1,9 @@ +// defineProperty can add array elements, bumping length + +var g = newGlobal(); +g.a = g.Array(0, 1, 2); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var aw = gw.getOwnPropertyDescriptor("a").value; +aw.defineProperty(3, {configurable: true, enumerable: true, writable: true, value: 3}); +assertEq(g.a.length, 4); diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-05.js b/js/src/jit-test/tests/debug/Object-defineProperty-05.js new file mode 100644 index 000000000..c7a3dde5e --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperty-05.js @@ -0,0 +1,20 @@ +// defineProperty can define accessor properties. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +g.value = undefined; +g.eval("function gf() { return 12; }\n" + + "function sf(v) { value = v; }\n"); +var gfw = gw.getOwnPropertyDescriptor("gf").value; +var sfw = gw.getOwnPropertyDescriptor("sf").value; +gw.defineProperty("x", {configurable: true, get: gfw, set: sfw}); +assertEq(g.x, 12); +g.x = 'ok'; +assertEq(g.value, 'ok'); + +var desc = g.Object.getOwnPropertyDescriptor(g, "x"); +assertEq(desc.configurable, true); +assertEq(desc.enumerable, false); +assertEq(desc.get, g.gf); +assertEq(desc.set, g.sf); diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-06.js b/js/src/jit-test/tests/debug/Object-defineProperty-06.js new file mode 100644 index 000000000..92aed6f1b --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperty-06.js @@ -0,0 +1,21 @@ +// obj.defineProperty with vague descriptors works like Object.defineProperty + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +gw.defineProperty("p", {configurable: true, enumerable: true}); +assertEq(g.p, undefined); +var desc = g.Object.getOwnPropertyDescriptor(g, "p"); +assertEq(desc.configurable, true); +assertEq(desc.enumerable, true); +assertEq(desc.value, undefined); +assertEq(desc.writable, false); + +gw.defineProperty("q", {}); +assertEq(g.q, undefined); +var desc = g.Object.getOwnPropertyDescriptor(g, "q"); +assertEq(desc.configurable, false); +assertEq(desc.enumerable, false); +assertEq(desc.value, undefined); +assertEq(desc.writable, false); diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-07.js b/js/src/jit-test/tests/debug/Object-defineProperty-07.js new file mode 100644 index 000000000..f75c6edf2 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperty-07.js @@ -0,0 +1,10 @@ +// obj.defineProperty throws if a value, getter, or setter is not a debuggee value. + +load(libdir + "asserts.js"); +var g = newGlobal(); +var dbg = new Debugger; +var gobj = dbg.addDebuggee(g); +assertThrowsInstanceOf(function () { gobj.defineProperty('x', {value: {}}); }, TypeError); +assertThrowsInstanceOf(function () { gobj.defineProperty('x', {get: Number}); }, TypeError); +assertThrowsInstanceOf(function () { gobj.defineProperty('x', {get: gobj, set: Number}) }, + TypeError); diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-08.js b/js/src/jit-test/tests/debug/Object-defineProperty-08.js new file mode 100644 index 000000000..b3226294e --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperty-08.js @@ -0,0 +1,10 @@ +// obj.defineProperty throws if a value, getter, or setter is in a different compartment than obj + +load(libdir + "asserts.js"); +var g1 = newGlobal(); +var g2 = newGlobal(); +var dbg = new Debugger; +var g1w = dbg.addDebuggee(g1); +var g2w = dbg.addDebuggee(g2); +assertThrowsInstanceOf(function () { g1w.defineProperty('x', {value: g2w}); }, TypeError); +assertThrowsInstanceOf(function () { g1w.defineProperty('x', {get: g1w, set: g2w}); }, TypeError); diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-09.js b/js/src/jit-test/tests/debug/Object-defineProperty-09.js new file mode 100644 index 000000000..c8493c6a1 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperty-09.js @@ -0,0 +1,24 @@ +// defineProperty can't re-define non-configurable properties. +// Also: when defineProperty throws, the exception is native to the debugger +// compartment, not a wrapper. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +gw.defineProperty("p", {value: 1}); +g.p = 4; +assertEq(g.p, 1); + +var threw; +try { + gw.defineProperty("p", {value: 2}); + threw = false; +} catch (exc) { + threw = true; + assertEq(exc instanceof TypeError, true); + assertEq(typeof exc.message, "string"); + assertEq(typeof exc.stack, "string"); +} +assertEq(threw, true); + +assertEq(g.p, 1); diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-10.js b/js/src/jit-test/tests/debug/Object-defineProperty-10.js new file mode 100644 index 000000000..62a7d63be --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperty-10.js @@ -0,0 +1,10 @@ +// defineProperty can make a non-configurable writable property non-writable + +load(libdir + "asserts.js"); +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +gw.defineProperty("p", {writable: true, value: 1}); +gw.defineProperty("p", {writable: false}); +g.p = 4; +assertEq(g.p, 1); diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-11.js b/js/src/jit-test/tests/debug/Object-defineProperty-11.js new file mode 100644 index 000000000..8b3205405 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperty-11.js @@ -0,0 +1,16 @@ +// obj.defineProperty works when obj's referent is a wrapper. + +var x = {}; +var g = newGlobal(); +g.x = x; +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var xw = gw.getOwnPropertyDescriptor("x").value; +xw.defineProperty("p", {configurable: true, enumerable: true, writable: true, value: gw}); +assertEq(x.p, g); + +var desc = Object.getOwnPropertyDescriptor(x, "p"); +assertEq(desc.configurable, true); +assertEq(desc.enumerable, true); +assertEq(desc.writable, true); +assertEq(desc.value, g); diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-12.js b/js/src/jit-test/tests/debug/Object-defineProperty-12.js new file mode 100644 index 000000000..84ec3fbb5 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperty-12.js @@ -0,0 +1,18 @@ +// obj.defineProperty redefining an existing property leaves unspecified attributes unchanged. + +var g = newGlobal(); +g.p = 1; +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +gw.defineProperty("p", {value: 2}); +assertEq(g.p, 2); + +var desc = Object.getOwnPropertyDescriptor(g, "p"); +assertEq(desc.configurable, true); +assertEq(desc.enumerable, true); +assertEq(desc.writable, true); +assertEq(desc.value, 2); + +g.p = 3; +assertEq(g.p, 3); diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-13.js b/js/src/jit-test/tests/debug/Object-defineProperty-13.js new file mode 100644 index 000000000..7da13e75c --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperty-13.js @@ -0,0 +1,16 @@ +// defineProperty throws if a getter or setter is neither undefined nor callable. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +for (let v of [null, false, 'bad', 0, 2.76, {}]) { + assertThrowsInstanceOf(function () { + gw.defineProperty("p", {configurable: true, get: v}); + }, TypeError); + assertThrowsInstanceOf(function () { + gw.defineProperty("p", {configurable: true, set: v}); + }, TypeError); +} diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-14.js b/js/src/jit-test/tests/debug/Object-defineProperty-14.js new file mode 100644 index 000000000..193824b08 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperty-14.js @@ -0,0 +1,15 @@ +// defineProperty accepts undefined for desc.get/set. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +gw.defineProperty("p", {get: undefined, set: undefined}); + +var desc = g.eval("Object.getOwnPropertyDescriptor(this, 'p')"); +assertEq("get" in desc, true); +assertEq("set" in desc, true); +assertEq(desc.get, undefined); +assertEq(desc.set, undefined); diff --git a/js/src/jit-test/tests/debug/Object-defineProperty-surfaces-01.js b/js/src/jit-test/tests/debug/Object-defineProperty-surfaces-01.js new file mode 100644 index 000000000..5bbea9122 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-defineProperty-surfaces-01.js @@ -0,0 +1,8 @@ +// Debugger.Object.prototype.defineProperty with too few arguments throws. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +assertThrowsInstanceOf(function () { gw.defineProperty("x"); }, TypeError); diff --git a/js/src/jit-test/tests/debug/Object-deleteProperty-01.js b/js/src/jit-test/tests/debug/Object-deleteProperty-01.js new file mode 100644 index 000000000..70ed69ce2 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-deleteProperty-01.js @@ -0,0 +1,17 @@ +// Basic deleteProperty tests. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +assertEq(gw.deleteProperty("no such property"), true); + +g.Object.defineProperty(g, "p", {configurable: true, value: 0}); +assertEq(gw.deleteProperty("p"), true); + +g[0] = 0; +assertEq(gw.deleteProperty(0), true); +assertEq("0" in g, false); + +assertEq(gw.deleteProperty(), false); // can't delete g.undefined +assertEq(g.undefined, undefined); diff --git a/js/src/jit-test/tests/debug/Object-deleteProperty-error-01.js b/js/src/jit-test/tests/debug/Object-deleteProperty-error-01.js new file mode 100644 index 000000000..9115f551c --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-deleteProperty-error-01.js @@ -0,0 +1,16 @@ +// Don't crash when a scripted proxy handler throws Error.prototype. + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + try { + frame.arguments[0].deleteProperty("x"); + } catch (exc) { + return; + } + throw new Error("deleteProperty should throw"); +}; + +g.eval("function h(obj) { debugger; }"); +g.eval("h(new Proxy({}, { deleteProperty() { throw Error.prototype; }}));"); + diff --git a/js/src/jit-test/tests/debug/Object-deleteProperty-error-02.js b/js/src/jit-test/tests/debug/Object-deleteProperty-error-02.js new file mode 100644 index 000000000..0048c2f57 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-deleteProperty-error-02.js @@ -0,0 +1,19 @@ +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + try { + frame.arguments[0].deleteProperty("x"); + } catch (exc) { + assertEq(exc instanceof Debugger.DebuggeeWouldRun, true); + return; + } + throw new Error("deleteProperty should throw"); +}; + +g.evaluate("function h(obj) { debugger; } \n" + + "h(new Proxy({}, \n" + + " { deleteProperty: function () { \n" + + " var e = new ReferenceError('diaf', 'fail'); \n" + + " throw e; \n" + + " } \n" + + " }));"); diff --git a/js/src/jit-test/tests/debug/Object-displayName-01.js b/js/src/jit-test/tests/debug/Object-displayName-01.js new file mode 100644 index 000000000..8e046d59c --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-displayName-01.js @@ -0,0 +1,17 @@ +// Debugger.Object.prototype.displayName + +var g = newGlobal(); +var dbg = Debugger(g); +var name; +dbg.onDebuggerStatement = function (frame) { name = frame.callee.displayName; }; + +g.eval("(function f() { debugger; })();"); +assertEq(name, "f"); +g.eval("(function () { debugger; })();"); +assertEq(name, undefined); +g.eval("Function('debugger;')();"); +assertEq(name, "anonymous"); +g.eval("var f = function() { debugger; }; f()"); +assertEq(name, "f"); +g.eval("var a = {}; a.f = function() { debugger; }; a.f()"); +assertEq(name, "a.f"); 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 000000000..d6883ad8c --- /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() +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 000000000..088bc09a8 --- /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() +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/jit-test/tests/debug/Object-errorLineNumber-errorColumnNumber.js b/js/src/jit-test/tests/debug/Object-errorLineNumber-errorColumnNumber.js new file mode 100644 index 000000000..eeda4f2a5 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-errorLineNumber-errorColumnNumber.js @@ -0,0 +1,55 @@ +// Debugger.Object.prototype.{errorLineNumber,errorColumnNumber} return the +// line number and column number associated with some error object. + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + +var syntaxError = gw.executeInGlobal("\nlet a, a;").throw; +assertEq(syntaxError.errorLineNumber, 2); +assertEq(syntaxError.errorColumnNumber, 7); + +var typeError = gw.executeInGlobal("\n1 + f();").throw; +assertEq(typeError.errorLineNumber, 2); +assertEq(typeError.errorColumnNumber, 1); + +// Custom errors have no line/column numbers . +var customError = gw.executeInGlobal("\nthrow 1;").throw; +assertEq(customError.errorLineNumber, undefined); +assertEq(customError.errorColumnNumber, undefined); + +customError = gw.executeInGlobal("\nthrow { errorLineNumber: 10, errorColumnNumber: 20 };").throw; +assertEq(customError.errorLineNumber, undefined); +assertEq(customError.errorColumnNumber, undefined); + +customError = gw.executeInGlobal("\nthrow { lineNumber: 10, columnNumber: 20 };").throw; +assertEq(customError.errorLineNumber, undefined); +assertEq(customError.errorColumnNumber, undefined); + +// Ensure that the method works across globals. +g.eval(`var g = newGlobal(); + g.eval('var err; \\n' + + 'try {\\n' + + ' f();\\n' + + '} catch (e) {\\n' + + ' err = e;\\n' + + '}'); + var err2 = g.err;`); +var otherGlobalError = gw.executeInGlobal("throw err2").throw; +assertEq(otherGlobalError.errorLineNumber, 3); +assertEq(otherGlobalError.errorColumnNumber, 3); + +// Ensure that non-error objects return undefined. +const Args = [ + "1", + "'blah'", + "({})", + "[]", + "() => 1" +] + +for (let arg of Args) { + let nonError = gw.executeInGlobal(`${arg}`).return; + assertEq(nonError.errorLineNumber, undefined); + assertEq(nonError.errorColumnNumber, undefined); +} diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-01.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-01.js new file mode 100644 index 000000000..a7f8efb41 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-01.js @@ -0,0 +1,13 @@ +// Debugger.Object.prototype.executeInGlobal basics + +var g = newGlobal(); +var h = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var hw = dbg.addDebuggee(h); + +g.y = "Bitte Orca"; +h.y = "Visiter"; +var y = "W H O K I L L"; +assertEq(gw.executeInGlobal('y').return, "Bitte Orca"); +assertEq(hw.executeInGlobal('y').return, "Visiter"); diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-02.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-02.js new file mode 100644 index 000000000..6623466fc --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-02.js @@ -0,0 +1,20 @@ +// Debugger.Object.prototype.executeInGlobal argument validation + +load(libdir + 'asserts.js'); + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); +var gobj = gw.makeDebuggeeValue(g.eval("({})")); + +assertThrowsInstanceOf(function () { gw.executeInGlobal(); }, TypeError); +assertThrowsInstanceOf(function () { gw.executeInGlobal(10); }, TypeError); +assertThrowsInstanceOf(function () { gobj.executeInGlobal('42'); }, TypeError); +assertEq(gw.executeInGlobal('42').return, 42); + +assertThrowsInstanceOf(function () { gw.executeInGlobalWithBindings(); }, TypeError); +assertThrowsInstanceOf(function () { gw.executeInGlobalWithBindings('42'); }, TypeError); +assertThrowsInstanceOf(function () { gw.executeInGlobalWithBindings(10, 1729); }, TypeError); +assertThrowsInstanceOf(function () { gw.executeInGlobalWithBindings('42', 1729); }, TypeError); +assertThrowsInstanceOf(function () { gobj.executeInGlobalWithBindings('42', {}); }, TypeError); +assertEq(gw.executeInGlobalWithBindings('42', {}).return, 42); diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-03.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-03.js new file mode 100644 index 000000000..a19005fb0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-03.js @@ -0,0 +1,19 @@ +// Debugger.Object.prototype.executeInGlobal: closures capturing the global + +var g = newGlobal(); +var h = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var hw = dbg.addDebuggee(h); + +g.x = "W H O K I L L"; +h.x = "No Color"; +var c1 = gw.executeInGlobal('(function () { return x; })').return; +var c2 = hw.executeInGlobal('(function () { return x; })').return; +var c3 = gw.executeInGlobalWithBindings('(function () { return x + y; })', { y:" In Rainbows" }).return; +var c4 = hw.executeInGlobalWithBindings('(function () { return x + y; })', { y:" In Rainbows" }).return; + +assertEq(c1.call(null).return, "W H O K I L L"); +assertEq(c2.call(null).return, "No Color"); +assertEq(c3.call(null).return, "W H O K I L L In Rainbows"); +assertEq(c4.call(null).return, "No Color In Rainbows"); diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-04.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-04.js new file mode 100644 index 000000000..fb8394586 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-04.js @@ -0,0 +1,55 @@ +// Debugger.Object.prototype.executeInGlobal: nested evals + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +assertEq(gw.executeInGlobal("eval('\"Awake\"');").return, "Awake"); + +// Evaluating non-strict-mode code uses the given global as its variable +// environment. +g.x = "Swing Lo Magellan"; +g.y = "The Milk-Eyed Mender"; +assertEq(gw.executeInGlobal("eval('var x = \"A Brief History of Love\"');\n" + + "var y = 'Merriweather Post Pavilion';" + + "x;").return, + "A Brief History of Love"); +assertEq(g.x, "A Brief History of Love"); +assertEq(g.y, "Merriweather Post Pavilion"); + +// As above, but notice that we still create bindings on the global, even +// when we've interposed a new environment via 'withBindings'. +g.x = "Swing Lo Magellan"; +g.y = "The Milk-Eyed Mender"; +assertEq(gw.executeInGlobalWithBindings("eval('var x = d1;'); var y = d2; x;", + { d1: "A Brief History of Love", + d2: "Merriweather Post Pavilion" }).return, + "A Brief History of Love"); +assertEq(g.x, "A Brief History of Love"); +assertEq(g.y, "Merriweather Post Pavilion"); + + +// Strict mode code variants of the above: + +// Strict mode still lets us create bindings on the global as this is +// equivalent to executing statements at the global level. But note that +// setting strict mode means that nested evals get their own call objects. +g.x = "Swing Lo Magellan"; +g.y = "The Milk-Eyed Mender"; +assertEq(gw.executeInGlobal("\'use strict\';\n" + + "eval('var x = \"A Brief History of Love\"');\n" + + "var y = \"Merriweather Post Pavilion\";" + + "x;").return, + "Swing Lo Magellan"); +assertEq(g.x, "Swing Lo Magellan"); +assertEq(g.y, "Merriweather Post Pavilion"); + +// Introducing a bindings object shouldn't change this behavior. +g.x = "Swing Lo Magellan"; +g.y = "The Milk-Eyed Mender"; +assertEq(gw.executeInGlobalWithBindings("'use strict'; eval('var x = d1;'); var y = d2; x;", + { d1: "A Brief History of Love", + d2: "Merriweather Post Pavilion" }).return, + "Swing Lo Magellan"); +assertEq(g.x, "Swing Lo Magellan"); +assertEq(g.y, "Merriweather Post Pavilion"); diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-05.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-05.js new file mode 100644 index 000000000..f89302010 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-05.js @@ -0,0 +1,22 @@ +// Debugger.Object.prototype.executeInGlobal throws when asked to evaluate in a CCW of a global. + +load(libdir + 'asserts.js'); + +var dbg = new Debugger(); + +var g1 = newGlobal(); +var dg1 = dbg.addDebuggee(g1); + +var g2 = newGlobal(); +var dg2 = dbg.addDebuggee(g2); + +// Generate a Debugger.Object viewing g2 from g1's compartment. +var dg1wg2 = dg1.makeDebuggeeValue(g2); +assertEq(dg1wg2.global, dg1); +assertEq(dg1wg2.unwrap(), dg2); +assertThrowsInstanceOf(function () { dg1wg2.executeInGlobal('1'); }, TypeError); +assertThrowsInstanceOf(function () { dg1wg2.executeInGlobalWithBindings('x', { x: 1 }); }, TypeError); + +// These, however, should not throw. +assertEq(dg1wg2.unwrap().executeInGlobal('1729').return, 1729); +assertEq(dg1wg2.unwrap().executeInGlobalWithBindings('x', { x: 1729 }).return, 1729); diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-06.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-06.js new file mode 100644 index 000000000..a7390aeb2 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-06.js @@ -0,0 +1,8 @@ +// Debugger.Object.prototype.executeInGlobal sets 'this' to the global. + +var dbg = new Debugger; +var g1 = newGlobal(); +var g1w = dbg.addDebuggee(g1); + +assertEq(g1w.executeInGlobal('this').return, g1w); +assertEq(g1w.executeInGlobalWithBindings('this', { x:42 }).return, g1w); diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-07.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-07.js new file mode 100644 index 000000000..5522e3b45 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-07.js @@ -0,0 +1,24 @@ +// executeInGlobal correctly handles optional custom url option +var g = newGlobal(); +var dbg = new Debugger(g); +var debuggee = dbg.getDebuggees()[0]; +var count = 0; + +function testUrl (options, expected) { + count++; + dbg.onNewScript = function(script){ + dbg.onNewScript = undefined; + assertEq(script.url, expected); + count--; + }; + debuggee.executeInGlobal("", options); +} + + +testUrl(undefined, "debugger eval code"); +testUrl(null, "debugger eval code"); +testUrl({ url: undefined }, "debugger eval code"); +testUrl({ url: null }, "null"); +testUrl({ url: 5 }, "5"); +testUrl({ url: "test" }, "test"); +assertEq(count, 0); diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-08.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-08.js new file mode 100644 index 000000000..61dc3a14b --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-08.js @@ -0,0 +1,22 @@ +// executeInGlobal correctly handles optional lineNumber option +var g = newGlobal(); +var dbg = new Debugger(g); +var debuggee = dbg.getDebuggees()[0]; +var count = 0; + +function testLineNumber (options, expected) { + count++; + dbg.onNewScript = function(script){ + dbg.onNewScript = undefined; + assertEq(script.startLine, expected); + count--; + }; + debuggee.executeInGlobal("", options); +} + + +testLineNumber(undefined, 1); +testLineNumber({}, 1); +testLineNumber({ lineNumber: undefined }, 1); +testLineNumber({ lineNumber: 5 }, 5); +assertEq(count, 0); diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-09.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-09.js new file mode 100644 index 000000000..f6b13c6a9 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-09.js @@ -0,0 +1,9 @@ +// The frame created for executeInGlobal is never marked as a 'FUNCTION' frame. + +(function () { + var g = newGlobal(); + var dbg = new Debugger; + var gw = dbg.addDebuggee(g); + gw.executeInGlobalWithBindings("eval('Math')",{}).return +})(); + diff --git a/js/src/jit-test/tests/debug/Object-executeInGlobal-10.js b/js/src/jit-test/tests/debug/Object-executeInGlobal-10.js new file mode 100644 index 000000000..904b62480 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-executeInGlobal-10.js @@ -0,0 +1,13 @@ +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +// executeInGlobal should be able to introduce and persist lexical bindings. +assertEq(gw.executeInGlobal(`let x = 42; x;`).return, 42); +assertEq(gw.executeInGlobal(`x;`).return, 42); + +// By contrast, Debugger.Frame.eval is like direct eval, and shouldn't be able +// to introduce new lexical bindings. +dbg.onDebuggerStatement = function (frame) { frame.eval(`let y = 84;`); }; +g.eval(`debugger;`); +assertEq(!!gw.executeInGlobal(`y;`).throw, true); diff --git a/js/src/jit-test/tests/debug/Object-forceLexicalInitializationByName.js b/js/src/jit-test/tests/debug/Object-forceLexicalInitializationByName.js new file mode 100644 index 000000000..4546a23d6 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-forceLexicalInitializationByName.js @@ -0,0 +1,61 @@ +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +let errorOne, errorTwo; + +function evalErrorStr(global, evalString) { + try { + global.evaluate(evalString); + return undefined; + } catch (e) { + return e.toString(); + } +} + + +assertEq(evalErrorStr(g, "let y = IDONTEXIST;"), "ReferenceError: IDONTEXIST is not defined"); +assertEq(evalErrorStr(g, "y = 1;"), + "ReferenceError: can't access lexical declaration `y' before initialization"); + +// Here we flip the uninitialized binding to undfined. +assertEq(gw.forceLexicalInitializationByName("y"), true); +assertEq(g.evaluate("y"), undefined); +g.evaluate("y = 1;"); +assertEq(g.evaluate("y"), 1); + +// Ensure that bogus bindings return false, but otherwise trigger no error or +// side effect. +assertEq(gw.forceLexicalInitializationByName("idontexist"), false); +assertEq(evalErrorStr(g, "idontexist"), "ReferenceError: idontexist is not defined"); + +// Ensure that ropes (non-atoms) behave properly +assertEq(gw.forceLexicalInitializationByName(("foo" + "bar" + "bop" + "zopple" + 2 + 3).slice(1)), + false); +assertEq(evalErrorStr(g, "let oobarbopzopple23 = IDONTEXIST;"), "ReferenceError: IDONTEXIST is not defined"); +assertEq(gw.forceLexicalInitializationByName(("foo" + "bar" + "bop" + "zopple" + 2 + 3).slice(1)), + true); +assertEq(g.evaluate("oobarbopzopple23"), undefined); + +// Ensure that only strings are accepted by forceLexicalInitializationByName +const bad_types = [ + 2112, + {geddy: "lee"}, + () => 1, + [], + Array, + "'1'", // non-identifier +] + +for (var badType of bad_types) { + assertThrowsInstanceOf(() => { + gw.forceLexicalInitializationByName(badType); + }, TypeError); +} + +// Finally, supplying no arguments should throw a type error +assertThrowsInstanceOf(() => { + Debugger.isCompilableUnit(); +}, TypeError); diff --git a/js/src/jit-test/tests/debug/Object-gc-01.js b/js/src/jit-test/tests/debug/Object-gc-01.js new file mode 100644 index 000000000..c2c6c51fc --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-gc-01.js @@ -0,0 +1,14 @@ +// Debugger.Objects keep their referents alive. + +var g = newGlobal(); +var dbg = Debugger(g); +var arr = []; +dbg.onDebuggerStatement = function (frame) { arr.push(frame.eval("[]").return); }; +g.eval("for (var i = 0; i < 10; i++) debugger;"); +assertEq(arr.length, 10); + +gc(); + +for (var i = 0; i < arr.length; i++) + assertEq(arr[i].class, "Array"); + diff --git a/js/src/jit-test/tests/debug/Object-getErrorMessageName.js b/js/src/jit-test/tests/debug/Object-getErrorMessageName.js new file mode 100644 index 000000000..bea9aad91 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-getErrorMessageName.js @@ -0,0 +1,29 @@ +// Debugger.Object.prototype.getErrorMessageName returns the error message name +// associated with some error object. + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + +assertEq(gw.executeInGlobal("(42).toString(0)").throw.errorMessageName, "JSMSG_BAD_RADIX"); + +// Custom errors have no error message name. +assertEq(gw.executeInGlobal("throw new Error()").throw.errorMessageName, undefined); + +// Ensure that the method works across globals. +g.eval(`var g = newGlobal(); + g.eval('var err; try { (42).toString(0); } catch (e) { err = e; }'); + var err2 = g.err;`); +assertEq(gw.executeInGlobal("throw err2").throw.errorMessageName, "JSMSG_BAD_RADIX"); + +// Ensure that non-error objects return undefined. +const Args = [ + "1", + "'blah'", + "({})", + "[]", + "() => 1" +] + +for (let arg of Args) + assertEq(gw.executeInGlobal(`${arg}`).return.errorMessageName, undefined); diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-01.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-01.js new file mode 100644 index 000000000..5a67074dd --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-01.js @@ -0,0 +1,59 @@ +// getOwnPropertyDescriptor works with simple data properties. + +var g = newGlobal(); +var dbg = Debugger(g); +var hits; +var expected; +dbg.onDebuggerStatement = function (frame) { + var args = frame.arguments; + var obj = args[0], id = args[1]; + var desc = obj.getOwnPropertyDescriptor(id); + if (expected === undefined) { + assertEq(desc, undefined); + } else { + assertEq(desc instanceof Object, true); + assertEq(desc.enumerable, expected.enumerable); + assertEq(desc.configurable, expected.configurable); + assertEq(desc.hasOwnProperty("value"), true); + assertEq(desc.value, expected.value); + assertEq(desc.writable, expected.writable === undefined ? true : expected.writable); + assertEq("get" in desc, false); + assertEq("set" in desc, false); + } + hits++; +}; + +g.eval("function f(obj, id) { debugger; }"); + +function test(obj, id, desc) { + expected = desc; + hits = 0; + g.f(obj, id); + assertEq(hits, 1); +} + +var obj = g.eval("({a: 1, ' ': undefined, '0': 0})"); +test(obj, "a", {value: 1, enumerable: true, configurable: true}); +test(obj, " ", {value: undefined, enumerable: true, configurable: true}); +test(obj, "b", undefined); +test(obj, "0", {value: 0, enumerable: true, configurable: true}); +test(obj, 0, {value: 0, enumerable: true, configurable: true}); + +var arr = g.eval("[7,,]"); +test(arr, 'length', {value: 2, enumerable: false, configurable: false}); +test(arr, 0, {value: 7, enumerable: true, configurable: true}); +test(arr, "0", {value: 7, enumerable: true, configurable: true}); +test(arr, 1, undefined); +test(arr, "1", undefined); +test(arr, 2, undefined); +test(arr, "2", undefined); +test(arr, "argelfraster", undefined); + +var re = g.eval("/erwe/"); +test(re, 'lastIndex', {value: 0, enumerable: false, configurable: false}); + +// String objects have a well-behaved resolve hook. +var str = g.eval("new String('hello world')"); +test(str, 'length', {value: 11, enumerable: false, configurable: false, writable: false}); +test(str, 0, {value: 'h', enumerable: true, configurable: false, writable: false}); +test(str, "0", {value: 'h', enumerable: true, configurable: false, writable: false}); diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-02.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-02.js new file mode 100644 index 000000000..9523cd5ea --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-02.js @@ -0,0 +1,8 @@ +// Property values that are objects are reflected as Debugger.Objects. + +var g = newGlobal(); +var dbg = Debugger(); +var gobj = dbg.addDebuggee(g); +g.self = g; +var desc = gobj.getOwnPropertyDescriptor("self"); +assertEq(desc.value, gobj); diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-03.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-03.js new file mode 100644 index 000000000..78f5d9efa --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-03.js @@ -0,0 +1,22 @@ +// obj.getOwnPropertyDescriptor works on global objects. + +var g = newGlobal(); +g.eval("var v;"); +this.eval("var v;"); + +var dbg = Debugger(); +var obj = dbg.addDebuggee(g); + +function test(name) { + var desc = obj.getOwnPropertyDescriptor(name); + assertEq(desc instanceof Object, true); + var expected = Object.getOwnPropertyDescriptor(this, name); + assertEq(Object.prototype.toString.call(desc), Object.prototype.toString.call(expected)); + assertEq(desc.enumerable, expected.enumerable); + assertEq(desc.configurable, expected.configurable); + assertEq(desc.writable, expected.writable); + assertEq(desc.value, expected.value); +} + +test("Infinity"); +test("v"); diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-04.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-04.js new file mode 100644 index 000000000..641e5b837 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-04.js @@ -0,0 +1,18 @@ +// obj.getOwnPropertyDescriptor works on accessor properties. + +var g = newGlobal(); +var dbg = new Debugger; +var gdo = dbg.addDebuggee(g); + +g.called = false; +g.eval("var a = {get x() { called = true; }};"); + +var desc = gdo.getOwnPropertyDescriptor("a").value.getOwnPropertyDescriptor("x"); +assertEq(g.called, false); +assertEq(desc.enumerable, true); +assertEq(desc.configurable, true); +assertEq("value" in desc, false); +assertEq("writable" in desc, false); +assertEq(desc.get instanceof Debugger.Object, true); +assertEq(desc.get.class, "Function"); +assertEq(desc.set, undefined); diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-05.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-05.js new file mode 100644 index 000000000..c8510c9d8 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-05.js @@ -0,0 +1,17 @@ +// obj.getOwnPropertyDescriptor presents getters and setters as Debugger.Object objects. + +var g = newGlobal(); +g.S = function foreignFunction(v) {}; +g.eval("var a = {};\n" + + "function G() {}\n" + + "Object.defineProperty(a, 'p', {get: G, set: S})"); + +var dbg = new Debugger; +var gdo = dbg.addDebuggee(g); +var desc = gdo.getOwnPropertyDescriptor("a").value.getOwnPropertyDescriptor("p"); +assertEq(desc.enumerable, false); +assertEq(desc.configurable, false); +assertEq("value" in desc, false); +assertEq("writable" in desc, false); +assertEq(desc.get, gdo.getOwnPropertyDescriptor("G").value); +assertEq(desc.set, gdo.getOwnPropertyDescriptor("S").value); diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-06.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-06.js new file mode 100644 index 000000000..98200c880 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-06.js @@ -0,0 +1,29 @@ +// obj.getOwnPropertyDescriptor works when obj is a transparent cross-compartment wrapper. + +var g1 = newGlobal(); +var g2 = newGlobal(); +g1.next = g2; + +// This test is a little hard to follow, especially the !== assertions. +// +// Bottom line: the value of a property of g1 can only be an object in g1's +// compartment, so any Debugger.Objects obtained by calling +// g1obj.getOwnPropertyDescriptor should all have referents in g1's +// compartment. + +var dbg = new Debugger; +var g1obj = dbg.addDebuggee(g1); +var g2obj = dbg.addDebuggee(g2); +var wobj = g1obj.getOwnPropertyDescriptor("next").value; +assertEq(wobj instanceof Debugger.Object, true); +assertEq(wobj !== g2obj, true); // referents are in two different compartments + +g2.x = "ok"; +assertEq(wobj.getOwnPropertyDescriptor("x").value, "ok"); + +g1.g2min = g2.min = g2.Math.min; +g2.eval("Object.defineProperty(this, 'y', {get: min});"); +assertEq(g2.y, Infinity); +var wmin = wobj.getOwnPropertyDescriptor("y").get; +assertEq(wmin !== g2obj.getOwnPropertyDescriptor("min").value, true); // as above +assertEq(wmin, g1obj.getOwnPropertyDescriptor("g2min").value); diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-surfaces-01.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-surfaces-01.js new file mode 100644 index 000000000..a3224739b --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-surfaces-01.js @@ -0,0 +1,14 @@ +// The argument to Debugger.Object.prototype.getOwnPropertyDescriptor can be omitted. + +var g = newGlobal(); +g.eval("var obj = {};"); + +var dbg = Debugger(g); +var obj; +dbg.onDebuggerStatement = function (frame) { obj = frame.eval("obj").return; }; +g.eval("debugger;"); + +assertEq(obj.getOwnPropertyDescriptor(), undefined); +g.obj.undefined = 17; +var desc = obj.getOwnPropertyDescriptor(); +assertEq(desc.value, 17); diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-surfaces-02.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-surfaces-02.js new file mode 100644 index 000000000..7da47e954 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyDescriptor-surfaces-02.js @@ -0,0 +1,14 @@ +// The argument to Debugger.Object.prototype.getOwnPropertyDescriptor can be an object. +var g = newGlobal(); +g.eval("var obj = {};"); + +var dbg = Debugger(g); +var obj; +dbg.onDebuggerStatement = function (frame) { obj = frame.eval("obj").return; }; +g.eval("debugger;"); + +var nameobj = {toString: function () { return 'x'; }}; +assertEq(obj.getOwnPropertyDescriptor(nameobj), undefined); +g.obj.x = 17; +var desc = obj.getOwnPropertyDescriptor(nameobj); +assertEq(desc.value, 17); diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyNames-01.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyNames-01.js new file mode 100644 index 000000000..9637e1ee0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyNames-01.js @@ -0,0 +1,33 @@ +// Basic getOwnPropertyNames tests. + +var g = newGlobal(); +var dbg = Debugger(); +var gobj = dbg.addDebuggee(g); + +function test(code) { + code = "(" + code + ");"; + var expected = Object.getOwnPropertyNames(eval(code)); + g.eval("obj = " + code); + var actual = gobj.getOwnPropertyDescriptor("obj").value.getOwnPropertyNames(); + assertEq(JSON.stringify(actual.sort()), JSON.stringify(expected.sort())); +} + +test("{}"); +test("{a: 0, b: 1}"); +test("{'name with space': 0}"); +test("{get x() {}, set y(v) {}}"); +test("{get x() { throw 'fit'; }}"); +test("Object.create({a: 1})"); +test("Object.create({get a() {}, set a(v) {}})"); +test("(function () { var x = {a: 0, b: 1}; delete a; return x; })()"); +test("Object.create(null, {x: {value: 0}})"); +test("[]"); +test("[0, 1, 2]"); +test("[,,,,,]"); +test("/a*a/"); +test("function () {}"); +test("(function () {\n" + + " var x = {};\n" + + " x[Symbol()] = 1; x[Symbol.for('moon')] = 2; x[Symbol.iterator] = 3;\n" + + " return x;\n" + + "})()"); diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertyNames-02.js b/js/src/jit-test/tests/debug/Object-getOwnPropertyNames-02.js new file mode 100644 index 000000000..8ed94e67a --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-getOwnPropertyNames-02.js @@ -0,0 +1,11 @@ +// obj.getOwnPropertyNames() works when obj's referent is itself a cross-compartment wrapper. + +var g = newGlobal(); +var dbg = Debugger(); +var gobj = dbg.addDebuggee(g); +g.p = {xyzzy: 8}; // makes a cross-compartment wrapper +g.p[Symbol.for("plugh")] = 9; +var wp = gobj.getOwnPropertyDescriptor("p").value; +var names = wp.getOwnPropertyNames(); +assertEq(names.length, 1); +assertEq(names[0], "xyzzy"); diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertySymbols-01.js b/js/src/jit-test/tests/debug/Object-getOwnPropertySymbols-01.js new file mode 100644 index 000000000..029c765b7 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-getOwnPropertySymbols-01.js @@ -0,0 +1,33 @@ +// Basic getOwnPropertSymbols tests. + +var g = newGlobal(); +var dbg = Debugger(); +var gobj = dbg.addDebuggee(g); + +function test(code) { + code = "(" + code + ");"; + var expected = Object.getOwnPropertySymbols(eval(code)); + g.eval("obj = " + code); + var actual = gobj.getOwnPropertyDescriptor("obj").value.getOwnPropertySymbols(); + + assertEq(JSON.stringify(actual.map((x) => x.toString()).sort()), + JSON.stringify(expected.map((x) => x.toString()).sort())); +} + +test("{}"); +test("Array.prototype"); // Symbol.iterator +test("Object.create(null)"); +test("(function() {let x = Symbol(); let o = {}; o[x] = 1; return o;})()"); +test("(function() {let x = Symbol('foo'); let o = {}; o[x] = 1; return o;})()"); +test("(function() {let x = Symbol('foo'); let y = Symbol('bar'); \ + let o = {}; o[x] = 1; o[y] = 2; return o;})()"); +test("(function() {let x = Symbol('foo with spaces'); \ + let o = {}; o[x] = 1; return o;})()"); +test("(function() {let x = Symbol('foo'); \ + let o = function(){}; o[x] = 1; return o;})()"); +test("(function() {let x = Symbol('foo'); \ + let o = Object.create(null); o[x] = 1; return o;})()"); +test("(function() {let x = Symbol('foo'); \ + let o = new Array(); o[x] = 1; return o;})()"); +test("(function() {let x = Symbol('foo'); \ + let o = {}; o[x] = 1; delete o[x]; return o;})()"); diff --git a/js/src/jit-test/tests/debug/Object-getOwnPropertySymbols-02.js b/js/src/jit-test/tests/debug/Object-getOwnPropertySymbols-02.js new file mode 100644 index 000000000..5f7175676 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-getOwnPropertySymbols-02.js @@ -0,0 +1,12 @@ +// obj.getOwnPropertySymbols() works when obj's referent is itself a cross-compartment wrapper. + +var g = newGlobal(); +var dbg = Debugger(); +var gobj = dbg.addDebuggee(g); +g.p = {xyzzy: 8}; // makes a cross-compartment wrapper +var sym = Symbol("plugh"); +g.p[sym] = 9; +var wp = gobj.getOwnPropertyDescriptor("p").value; +var names = wp.getOwnPropertySymbols(); +assertEq(names.length, 1); +assertEq(names[0], sym); diff --git a/js/src/jit-test/tests/debug/Object-global-01.js b/js/src/jit-test/tests/debug/Object-global-01.js new file mode 100644 index 000000000..24a5a5f32 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-global-01.js @@ -0,0 +1,26 @@ +// Debugger.Object.prototype.global accessor surfaces. + +load(libdir + 'asserts.js'); + +var dbg = new Debugger; +var g = newGlobal(); +var gw = dbg.addDebuggee(g); + +assertEq(Object.getOwnPropertyDescriptor(gw, 'global'), undefined); +var d = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(gw), 'global'); +assertEq(d.enumerable, false); +assertEq(d.configurable, true); +assertEq(typeof d.get, "function"); +assertEq(d.get.length, 0); +assertEq(d.set, undefined); + +// This should not throw. +gw.global = ''; + +// This should throw. +assertThrowsInstanceOf(function () { "use strict"; gw.global = {}; }, TypeError); +assertEq(gw.global, gw); + +// You shouldn't be able to apply the accessor to the prototype. +assertThrowsInstanceOf(function () { return Debugger.Object.prototype.global; }, + TypeError); diff --git a/js/src/jit-test/tests/debug/Object-global-02.js b/js/src/jit-test/tests/debug/Object-global-02.js new file mode 100644 index 000000000..68fc1b166 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-global-02.js @@ -0,0 +1,25 @@ +// Debugger.Object.prototype.global retrieves the correct global. + +var dbg = new Debugger; +var g1 = newGlobal(); +var g1w = dbg.addDebuggee(g1); +var g2 = newGlobal(); +var g2w = dbg.addDebuggee(g2); + +assertEq(g1w === g2w, false); +assertEq(g1w.global, g1w); +assertEq(g2w.global, g2w); + +var g1ow = g1w.makeDebuggeeValue(g1.Object()); +var g2ow = g2w.makeDebuggeeValue(g2.Object()); +assertEq(g1ow.global, g1w); +assertEq(g2ow.global, g2w); + +// mild paranoia +assertEq(g1ow.global === g1ow, false); +assertEq(g2ow.global === g2ow, false); + +// The .global accessor doesn't unwrap. +assertEq(g1w.makeDebuggeeValue(g2.Object()).global, g1w); +assertEq(g2w.makeDebuggeeValue(g1.Object()).global, g2w); + diff --git a/js/src/jit-test/tests/debug/Object-identity-01.js b/js/src/jit-test/tests/debug/Object-identity-01.js new file mode 100644 index 000000000..a4c48e420 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-identity-01.js @@ -0,0 +1,10 @@ +// Two references to the same object get the same Debugger.Object wrapper. +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.arguments[0], frame.arguments[1]); + hits++; +}; +g.eval("var obj = {}; function f(a, b) { debugger; } f(obj, obj);"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Object-identity-02.js b/js/src/jit-test/tests/debug/Object-identity-02.js new file mode 100644 index 000000000..ec7f07a4b --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-identity-02.js @@ -0,0 +1,10 @@ +// Different objects get different Debugger.Object wrappers. +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.arguments[0] === frame.arguments[1], false); + hits++; +}; +g.eval("function f(a, b) { debugger; } f({}, {});"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Object-identity-03.js b/js/src/jit-test/tests/debug/Object-identity-03.js new file mode 100644 index 000000000..d3e8fc08b --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-identity-03.js @@ -0,0 +1,25 @@ +// The same object gets the same Debugger.Object wrapper at different times, if the difference would be observable. + +var N = 12; + +var g = newGlobal(); +var dbg = Debugger(g); +var wrappers = []; + +dbg.onDebuggerStatement = function (frame) { wrappers.push(frame.arguments[0]); }; +g.eval("var originals = []; function f(x) { originals.push(x); debugger; }"); +for (var i = 0; i < N; i++) + g.eval("f({});"); +assertEq(wrappers.length, N); + +for (var i = 0; i < N; i++) + for (var j = i + 1; j < N; j++) + assertEq(wrappers[i] === wrappers[j], false); + +gc(); + +dbg.onDebuggerStatement = function (frame) { assertEq(frame.arguments[0], wrappers.pop()); }; +g.eval("function h(x) { debugger; }"); +for (var i = 0; i < N; i++) + g.eval("h(originals.pop());"); +assertEq(wrappers.length, 0); diff --git a/js/src/jit-test/tests/debug/Object-isArrowFunction.js b/js/src/jit-test/tests/debug/Object-isArrowFunction.js new file mode 100644 index 000000000..34c4a1de5 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-isArrowFunction.js @@ -0,0 +1,22 @@ +// Debugger.Object.prototype.isArrowFunction recognizes arrow functions. + +var g = newGlobal(); +var dbg = new Debugger; +var gDO = dbg.addDebuggee(g); +var hits = 0; + +function checkIsArrow(shouldBe, expr) { + print(expr); + assertEq(gDO.executeInGlobal(expr).return.isArrowFunction, shouldBe); +} + +checkIsArrow(true, '() => { }'); +checkIsArrow(true, '(a) => { bleh; }'); +checkIsArrow(false, 'Object.getPrototypeOf(() => { })'); +checkIsArrow(false, '(function () { })'); +checkIsArrow(false, 'function f() { } f'); +checkIsArrow((void 0), '({})'); +checkIsArrow(false, 'Math.atan2'); +checkIsArrow(false, 'Function.prototype'); +checkIsArrow(false, 'Function("")'); +checkIsArrow(false, 'new Function("")'); diff --git a/js/src/jit-test/tests/debug/Object-makeDebuggeeValue-01.js b/js/src/jit-test/tests/debug/Object-makeDebuggeeValue-01.js new file mode 100644 index 000000000..5537150e3 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-makeDebuggeeValue-01.js @@ -0,0 +1,42 @@ +// Debugger.Object.prototype.makeDebuggeeValue creates only one +// Debugger.Object instance for each debuggee object. + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + +g.eval("var x = { 'now playing': 'Joy Division' };"); +g.eval("var y = { 'mood': 'bleak' };"); + +wx = gw.makeDebuggeeValue(g.x); +assertEq(wx, gw.makeDebuggeeValue(g.x)); +assertEq(wx === g.x, false); +assertEq("now playing" in wx, false); +assertEq(wx.getOwnPropertyNames().indexOf("now playing"), 0); +wx.commentary = "deconstruction"; +assertEq("deconstruction" in g.x, false); + +wy = gw.makeDebuggeeValue(g.y); +assertEq(wy === wx, false); +wy.commentary = "reconstruction"; +assertEq(wx.commentary, "deconstruction"); + +// Separate debuggers get separate Debugger.Object instances, but both +// instances' referents are the same underlying object. +var dbg2 = new Debugger(); +var gw2 = dbg2.addDebuggee(g); +w2x = gw2.makeDebuggeeValue(g.x); +assertEq(wx === w2x, false); +w2x.defineProperty("breadcrumb", { value: "pumpernickel" }); +assertEq(wx.getOwnPropertyDescriptor("breadcrumb").value, "pumpernickel"); + +// Non-objects are already debuggee values. +assertEq(gw.makeDebuggeeValue("foonting turlingdromes"), "foonting turlingdromes"); +assertEq(gw.makeDebuggeeValue(true), true); +assertEq(gw.makeDebuggeeValue(false), false); +assertEq(gw.makeDebuggeeValue(null), null); +assertEq(gw.makeDebuggeeValue(1729), 1729); +assertEq(gw.makeDebuggeeValue(Math.PI), Math.PI); +assertEq(gw.makeDebuggeeValue(undefined), undefined); +var s = g.eval("Symbol('Stavromula Beta')"); +assertEq(gw.makeDebuggeeValue(s), s); diff --git a/js/src/jit-test/tests/debug/Object-makeDebuggeeValue-02.js b/js/src/jit-test/tests/debug/Object-makeDebuggeeValue-02.js new file mode 100644 index 000000000..ef3e099b8 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-makeDebuggeeValue-02.js @@ -0,0 +1,12 @@ +// Debugger.Object.prototype.makeDebuggeeValue returns the object wrapped +// the same way as Debugger.Frame.prototype.eval, etc. +var g = newGlobal(); +g.eval("function f() { debugger; }"); +var dbg = Debugger(); +var gw = dbg.addDebuggee(g); +var jsonw; +dbg.onDebuggerStatement = function (frame) { + jsonw = frame.eval("JSON").return; +}; +g.eval("debugger;"); +assertEq(gw.makeDebuggeeValue(g.JSON), jsonw); diff --git a/js/src/jit-test/tests/debug/Object-name-01.js b/js/src/jit-test/tests/debug/Object-name-01.js new file mode 100644 index 000000000..eb68b019b --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-name-01.js @@ -0,0 +1,13 @@ +// Debugger.Object.prototype.name + +var g = newGlobal(); +var dbg = Debugger(g); +var name; +dbg.onDebuggerStatement = function (frame) { name = frame.callee.name; }; + +g.eval("(function f() { debugger; })();"); +assertEq(name, "f"); +g.eval("(function () { debugger; })();"); +assertEq(name, undefined); +g.eval("Function('debugger;')();"); +assertEq(name, "anonymous"); diff --git a/js/src/jit-test/tests/debug/Object-name-02.js b/js/src/jit-test/tests/debug/Object-name-02.js new file mode 100644 index 000000000..2406c3d55 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-name-02.js @@ -0,0 +1,16 @@ +// The .name of a non-function object is undefined. + +var g = newGlobal(); +var hits = 0; +var dbg = new Debugger(g); +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.arguments[0].name, undefined); + hits++; +}; +g.eval("function f(nonfunction) { debugger; }"); + +g.eval("f({});"); +g.eval("f(/a*/);"); +g.eval("f({name: 'bad'});"); +g.eval("f(new Proxy({}, {}));"); +assertEq(hits, 4); diff --git a/js/src/jit-test/tests/debug/Object-parameterNames.js b/js/src/jit-test/tests/debug/Object-parameterNames.js new file mode 100644 index 000000000..24c0702d1 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-parameterNames.js @@ -0,0 +1,33 @@ +load(libdir + 'array-compare.js'); + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var arr = frame.arguments; + assertEq(arraysEqual(arr[0].parameterNames, []), true); + assertEq(arraysEqual(arr[1].parameterNames, ["x"]), true); + assertEq(arraysEqual(arr[2].parameterNames, + ["a","b","c","d","e","f","g","h","i","j","k","l","m", + "n","o","p","q","r","s","t","u","v","w","x","y","z"]), + true); + assertEq(arraysEqual(arr[3].parameterNames, ["a", (void 0), (void 0)]), true); + assertEq(arr[4].parameterNames, (void 0)); + assertEq(arraysEqual(arr[5].parameterNames, [(void 0), (void 0)]), true); + assertEq(arr.length, 6); + hits++; +}; + +g.eval("(" + + function () { + (function () { debugger; } + (function () {}, + function (x) {}, + function (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z) {}, + function (a, [b, c], {d, e:f}) { }, + {a:1}, + Math.atan2 + )); + } + +")()"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Object-preventExtensions-01.js b/js/src/jit-test/tests/debug/Object-preventExtensions-01.js new file mode 100644 index 000000000..729a72262 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-preventExtensions-01.js @@ -0,0 +1,17 @@ +// Basic preventExtensions test. + +var g = newGlobal(); +var obj = g.eval("({x: 1})"); +assertEq(g.Object.isExtensible(obj), true); + +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var objw = gw.makeDebuggeeValue(obj); +assertEq(objw.isExtensible(), true); + +assertEq(objw.preventExtensions(), undefined); +assertEq(g.Object.isExtensible(obj), false); +assertEq(objw.isExtensible(), false); + +// Calling preventExtensions again has no effect. +assertEq(objw.preventExtensions(), undefined); diff --git a/js/src/jit-test/tests/debug/Object-proto.js b/js/src/jit-test/tests/debug/Object-proto.js new file mode 100644 index 000000000..8d1791445 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-proto.js @@ -0,0 +1,23 @@ +// Debugger.Object.prototype.proto +var g = newGlobal(); +var dbgeval = function () { + var dbg = new Debugger(g); + var hits = 0; + g.eval("function f() { debugger; }"); + var lastval; + dbg.onDebuggerStatement = function (frame) { lastval = frame.arguments[0]; }; + return function dbgeval(s) { + g.eval("f(" + s + ");"); + return lastval; + }; + }(); + +var Op = dbgeval("Object.prototype"); +assertEq(Op.proto, null); +assertEq(dbgeval("({})").proto, Op); + +var Ap = dbgeval("[]").proto; +assertEq(Ap, dbgeval("Array.prototype")); +assertEq(Ap.proto, Op); + +assertEq(dbgeval("Object").proto, dbgeval("Function.prototype")); diff --git a/js/src/jit-test/tests/debug/Object-proxy.js b/js/src/jit-test/tests/debug/Object-proxy.js new file mode 100644 index 000000000..b8296fe6d --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-proxy.js @@ -0,0 +1,44 @@ +// Debugger.Object.prototype.isProxy recognizes (scripted) proxies. +// Debugger.Object.prototype.proxyTarget exposes the [[Proxytarget]] of a proxy. +// Debugger.Object.prototype.proxyHandler exposes the [[ProxyHandler]] of a proxy. + +var g = newGlobal(); +var dbg = new Debugger; +var gDO = dbg.addDebuggee(g); + +g.eval('var target = [1,2,3];'); +g.eval('var handler = {has: ()=>false};'); +g.eval('var proxy = new Proxy(target, handler);'); +g.eval('var proxyProxy = new Proxy(proxy, proxy);'); +g.eval('var revoker = Proxy.revocable(target, handler);'); +g.eval('var revocable = revoker.proxy;'); + +var target = gDO.getOwnPropertyDescriptor('target').value; +var handler = gDO.getOwnPropertyDescriptor('handler').value; +var proxy = gDO.getOwnPropertyDescriptor('proxy').value; +var proxyProxy = gDO.getOwnPropertyDescriptor('proxyProxy').value; +var revocable = gDO.getOwnPropertyDescriptor('revocable').value; + +assertEq(target.isProxy, false); +assertEq(target.proxyTarget, undefined); +assertEq(target.proxyHandler, undefined); + +assertEq(handler.isProxy, false); +assertEq(handler.proxyTarget, undefined); +assertEq(handler.proxyHandler, undefined); + +assertEq(proxy.isProxy, true); +assertEq(proxy.proxyTarget, target); +assertEq(proxy.proxyHandler, handler); + +assertEq(proxyProxy.isProxy, true); +assertEq(proxyProxy.proxyTarget, proxy); +assertEq(proxyProxy.proxyHandler, proxy); + +assertEq(revocable.isProxy, true); +assertEq(revocable.proxyTarget, target); +assertEq(revocable.proxyHandler, handler); +g.eval('revoker.revoke();'); +assertEq(revocable.isProxy, true); +assertEq(revocable.proxyTarget, null); +assertEq(revocable.proxyHandler, null); diff --git a/js/src/jit-test/tests/debug/Object-script-AsmJSNative.js b/js/src/jit-test/tests/debug/Object-script-AsmJSNative.js new file mode 100644 index 000000000..650b71374 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-script-AsmJSNative.js @@ -0,0 +1,15 @@ +function test(stdlib, foreign) { + "use asm" + function f(y) { + y = +y; + } + return f; +}; +var g = newGlobal(); +g.parent = this; +g.eval(` + var dbg = new Debugger(); + var parentw = dbg.addDebuggee(parent); + var testw = parentw.makeDebuggeeValue(parent.test); + var scriptw = testw.script; +`); diff --git a/js/src/jit-test/tests/debug/Object-script-environment-nondebuggee.js b/js/src/jit-test/tests/debug/Object-script-environment-nondebuggee.js new file mode 100644 index 000000000..5e4643e38 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-script-environment-nondebuggee.js @@ -0,0 +1,24 @@ +// The script and environment of a non-debuggee function are null. + +var g = newGlobal(); +g.eval('function f() { return "from f"; }'); + +var dbg = new Debugger; +var gw = dbg.makeGlobalObjectReference(g); +var fw = gw.getOwnPropertyDescriptor('f').value; + +// g is not a debuggee, so we can't fetch f's script or environment. +assertEq(fw.script, null); +assertEq(fw.environment, null); + +// If we add g as a debuggee, we can fetch f's script and environment. +dbg.addDebuggee(g); +var fscript = fw.script; +var fenv = fw.environment; +assertEq(fscript instanceof Debugger.Script, true); +assertEq(fenv instanceof Debugger.Environment, true); + +// Removing g as a debuggee makes the script and environment inaccessible again. +dbg.removeDebuggee(g); +assertEq(fw.script, null); +assertEq(fw.environment, null); diff --git a/js/src/jit-test/tests/debug/Object-script-lazy.js b/js/src/jit-test/tests/debug/Object-script-lazy.js new file mode 100644 index 000000000..b58dc38ad --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-script-lazy.js @@ -0,0 +1,61 @@ +// Asking for the script of a Debugger.Object should work, even when lazy +// functions are involved. + +// Note that we never hand out the scripts of non-debuggee functions, and +// putting a compartment in debug mode automatically de-lazifies all its +// functions. (This ensures that findScripts works, too.) +// +// So here we create a reference to a non-debuggee function, verify that we +// can't access its interesting properties, make it a debuggee, and verify +// that everything becomes available. + +// Create functions f, g in a non-debuggee compartment. +var g1 = newGlobal(); +g1.eval('function f() { return "from f"; }'); +g1.eval('function g() { return "from g"; }'); +g1.eval('this.h = f.bind(g, 42, "foo");'); + +// Create a debuggee compartment with CCWs referring to f and g. +var g2 = newGlobal(); +var dbg = new Debugger; +var g2w = dbg.addDebuggee(g2); +g2.f = g1.f; +g2.g = g1.g; +g2.h = g1.h; + +// At this point, g1.f should still be a lazy function. Unwrapping a D.O +// referring to g2's CCW of f should yield a D.O referring to f directly. +// Asking for that second D.O's script should yield null, because it's not +// a debuggee. +var fDO = g2w.getOwnPropertyDescriptor('f').value; +assertEq(fDO.global, g2w); +assertEq(fDO.unwrap().global === g2w, false); +assertEq(fDO.unwrap().script, null); + +// Similarly for g1.g, and asking for its parameter names. +var gDO = g2w.getOwnPropertyDescriptor('g').value; +assertEq(gDO.global, g2w); +assertEq(gDO.unwrap().global === g2w, false); +assertEq(gDO.unwrap().parameterNames, undefined); + +// Similarly for g1.h, and asking for its bound function properties. +var hDO = g2w.getOwnPropertyDescriptor('h').value; +assertEq(hDO.global, g2w); +assertEq(hDO.unwrap().global === g2w, false); +assertEq(hDO.unwrap().isBoundFunction, undefined); +assertEq(hDO.unwrap().isArrowFunction, undefined); +assertEq(hDO.unwrap().boundTargetFunction, undefined); +assertEq(hDO.unwrap().boundThis, undefined); +assertEq(hDO.unwrap().boundArguments, undefined); + +// Add g1 as a debuggee, and verify that we can get everything. +dbg.addDebuggee(g1); +assertEq(fDO.unwrap().script instanceof Debugger.Script, true); +assertEq(gDO.unwrap().parameterNames instanceof Array, true); +assertEq(hDO.unwrap().isBoundFunction, true); +assertEq(hDO.unwrap().isArrowFunction, false); +assertEq(hDO.unwrap().boundTargetFunction, fDO.unwrap()); +assertEq(hDO.unwrap().boundThis, gDO.unwrap()); +assertEq(hDO.unwrap().boundArguments.length, 2); +assertEq(hDO.unwrap().boundArguments[0], 42); +assertEq(hDO.unwrap().boundArguments[1], "foo"); diff --git a/js/src/jit-test/tests/debug/Object-script.js b/js/src/jit-test/tests/debug/Object-script.js new file mode 100644 index 000000000..a73032f93 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-script.js @@ -0,0 +1,13 @@ +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var arr = frame.arguments; + assertEq(arr[0].script instanceof Debugger.Script, true); + assertEq(arr[1].script, undefined); + assertEq(arr[2].script, undefined); + hits++; +}; + +g.eval("(function () { debugger; })(function g(){}, {}, Math.atan2);"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Object-seal-01.js b/js/src/jit-test/tests/debug/Object-seal-01.js new file mode 100644 index 000000000..760060208 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-seal-01.js @@ -0,0 +1,63 @@ +// Basic tests for obj.{seal,freeze,preventExtensions,isSealed,isFrozen,isExtensible}. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +g.eval("function compareObjects() {\n" + + " assertEq(Object.isExtensible(x), Object.isExtensible(y));\n" + + " var xnames = Object.getOwnPropertyNames(x).sort();\n" + + " var ynames = Object.getOwnPropertyNames(y).sort();\n" + + " assertEq(xnames.length, ynames.length);\n" + + " for (var i = 0; i < xnames.length; i++) {\n" + + " assertEq(xnames[i], ynames[i]);\n" + + " var name = xnames[i];\n" + + " var xd = Object.getOwnPropertyDescriptor(x, name);\n" + + " var yd = Object.getOwnPropertyDescriptor(y, name);\n" + + " assertEq(xd.configurable, yd.configurable, code + '.' + name + ' .configurable');\n" + + " assertEq(xd.enumerable, yd.enumerable, code + '.' + name + ' .enumerable');\n" + + " assertEq(xd.writable, yd.writable, code + '.' + name + ' .writable');\n" + + " }\n" + + "}\n"); + +function test(code) { + g.code = code; + g.eval("x = (" + code + ");"); + g.eval("y = (" + code + ");"); + var xw = gw.getOwnPropertyDescriptor("x").value; + + function check() { + // The Debugger.Object seal/freeze/preventExtensions methods + // had the same effect as the corresponding ES5 Object methods. + g.compareObjects(); + + // The Debugger.Object introspection methods agree with the ES5 Object methods. + assertEq(xw.isExtensible(), g.Object.isExtensible(g.x), code + ' isExtensible'); + assertEq(xw.isSealed(), g.Object.isSealed(g.x), code + ' isSealed'); + assertEq(xw.isFrozen(), g.Object.isFrozen(g.x), code + ' isFrozen'); + } + + check(); + + xw.preventExtensions(); + assertEq(g.Object.isExtensible(g.x), false, code + ' preventExtensions'); + g.Object.preventExtensions(g.y); + check(); + + xw.seal(); + assertEq(g.Object.isSealed(g.x), true, code + ' seal'); + g.Object.seal(g.y); + check(); + + xw.freeze(); + assertEq(g.Object.isFrozen(g.x), true, code + ' freeze'); + g.Object.freeze(g.y); + check(); +} + +test("{}"); +test("{a: [1], get b() { return -1; }}"); +test("Object.create(null, {x: {value: 3}, y: {get: Math.min}})"); +test("[]"); +test("[,,,,,]"); +test("[0, 1, 2]"); diff --git a/js/src/jit-test/tests/debug/Object-unsafeDereference-01.js b/js/src/jit-test/tests/debug/Object-unsafeDereference-01.js new file mode 100644 index 000000000..7e560b665 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-unsafeDereference-01.js @@ -0,0 +1,10 @@ +// Debugger.Object.prototype.unsafeDereference returns the referent directly. + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + +assertEq(gw.getOwnPropertyDescriptor('Math').value.unsafeDereference(), g.Math); + +g.eval('var obj = {}'); +assertEq(gw.getOwnPropertyDescriptor('obj').value.unsafeDereference(), g.obj); diff --git a/js/src/jit-test/tests/debug/Object-unwrap-01.js b/js/src/jit-test/tests/debug/Object-unwrap-01.js new file mode 100644 index 000000000..f9668c5c2 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-unwrap-01.js @@ -0,0 +1,23 @@ +// Check Debugger.Object.prototype.unwrap surfaces. + +load(libdir + 'asserts.js'); + +var dbg = new Debugger(); +var g = newGlobal(); +var gw = dbg.addDebuggee(g); + +assertEq(Object.getOwnPropertyDescriptor(gw, 'unwrap'), undefined); +var d = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(gw), 'unwrap'); +assertEq(d.enumerable, false); +assertEq(d.configurable, true); +assertEq(d.writable, true); + +assertEq(typeof gw.unwrap, "function"); +assertEq(gw.unwrap.length, 0); +assertEq(gw.unwrap.name, "unwrap"); + +// It can be called. +gw.unwrap(); + +// You shouldn't be able to apply the accessor to the prototype. +assertThrowsInstanceOf(function () { Debugger.Object.prototype.unwrap(); }, TypeError); diff --git a/js/src/jit-test/tests/debug/Object-unwrap-02.js b/js/src/jit-test/tests/debug/Object-unwrap-02.js new file mode 100644 index 000000000..1f1c0e586 --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-unwrap-02.js @@ -0,0 +1,23 @@ +// Debugger.Object.prototype.unwrap unwraps Debugger.Objects referring to +// cross-compartment wrappers. + +var dbg = new Debugger(); + +var g1 = newGlobal(); +var dg1 = dbg.addDebuggee(g1); +assertEq(dg1.unwrap(), dg1); + +var g2 = newGlobal(); +var dg2 = dbg.addDebuggee(g2); + +var dg1g2 = dg1.makeDebuggeeValue(g2); +assertEq(dg1g2.global, dg1); +assertEq(dg1g2.unwrap(), dg2); + +// Try an ordinary object, not a global. +var g2o = g2.Object(); +var dg2o = dg2.makeDebuggeeValue(g2o); +var dg1g2o = dg1.makeDebuggeeValue(g2o); +assertEq(dg1g2o.global, dg1); +assertEq(dg1g2o.unwrap(), dg2o); +assertEq(dg1g2o.unwrap().unwrap(), dg2o); diff --git a/js/src/jit-test/tests/debug/Object-unwrap-03.js b/js/src/jit-test/tests/debug/Object-unwrap-03.js new file mode 100644 index 000000000..cdb6d84ef --- /dev/null +++ b/js/src/jit-test/tests/debug/Object-unwrap-03.js @@ -0,0 +1,15 @@ +// Debugger.Object.prototype.unwrap should not let us see things in +// invisible-to-Debugger compartments. + +load(libdir + 'asserts.js'); + +var g = newGlobal({ invisibleToDebugger: true }); + +var dbg = new Debugger; + +// Create a wrapper in our compartment for the global. +// Note that makeGlobalObjectReference won't do: it tries to dereference as far +// as it can go. +var /* yo */ DOwg = dbg.makeGlobalObjectReference(this).makeDebuggeeValue(g); + +assertThrowsInstanceOf(() => DOwg.unwrap(), Error); diff --git a/js/src/jit-test/tests/debug/RematerializedFrame-retval.js b/js/src/jit-test/tests/debug/RematerializedFrame-retval.js new file mode 100644 index 000000000..ce16404d1 --- /dev/null +++ b/js/src/jit-test/tests/debug/RematerializedFrame-retval.js @@ -0,0 +1,39 @@ +// |jit-test| error: InternalError; --baseline-eager; --ion-eager +// Make sure that return values we store in RematerializedFrames via resumption +// values get propagated to the BaselineFrames we build from them. +// +// Test case from bug 1285939; there's another in bug 1282518, but this one +// takes less time to run, when it doesn't crash. + +var lfLogBuffer = ` +function testResumptionVal(resumptionVal, turnOffDebugMode) { + var g = newGlobal(); + var dbg = new Debugger; + setInterruptCallback(function () { + dbg.addDebuggee(g); + var frame = dbg.getNewestFrame(); + frame.onStep = function () { + frame.onStep = undefined; + return resumptionVal; + }; + return true; + }); + try { + return g.eval("(" + function f() { + invokeInterruptCallback(function (interruptRv) { + f({ valueOf: function () { dbg.g(dbg); }}); + }); + } + ")();"); + } finally { } +} +assertEq(testResumptionVal({ return: "not 42" }), "not 42"); +`; +loadFile(lfLogBuffer); +function loadFile(lfVarx) { + try { + let m = parseModule(lfVarx); + m.declarationInstantiation(); + m.evaluation(); + } catch (lfVare) {} +} + diff --git a/js/src/jit-test/tests/debug/Script-01.js b/js/src/jit-test/tests/debug/Script-01.js new file mode 100644 index 000000000..330ff8a54 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-01.js @@ -0,0 +1,70 @@ +// We get the same Debugger.Script object instance each time we ask. + +var global = newGlobal(); +global.eval('function f() { debugger; }'); +global.eval('function g() { debugger; }'); + +var debug = new Debugger(global); + +function evalAndNoteScripts(prog) { + var scripts = {}; + debug.onDebuggerStatement = function(frame) { + if (frame.type == "call") + assertEq(frame.script, frame.callee.script); + scripts.frame = frame.script; + if (frame.arguments[0]) + scripts.argument = frame.arguments[0].script; + }; + global.eval(prog); + return scripts; +} + +// If we create a frame for a function and pass it as a value, those should +// both yield the same Debugger.Script instance. +var scripts = evalAndNoteScripts('f(f)'); +assertEq(scripts.frame, scripts.argument); +var fScript = scripts.argument; + +// If we call a second time, we should still get the same instance. +scripts = evalAndNoteScripts('f(f)'); +assertEq(scripts.frame, fScript); +assertEq(scripts.argument, fScript); + +// If we call with a different argument, we should get a different Debugger.Script. +scripts = evalAndNoteScripts('f(g)'); +assertEq(scripts.frame !== scripts.argument, true); +assertEq(scripts.frame, fScript); +var gScript = scripts.argument; + +// See if we can get g via the frame. +scripts = evalAndNoteScripts('g(f)'); +assertEq(scripts.frame !== scripts.argument, true); +assertEq(scripts.frame, gScript); +assertEq(scripts.argument, fScript); + +// Different closures made from the same 'function' expression should yield +// the same script. +global.eval('function gen1(x) { return function clo(y) { return x+y; }; }'); +global.eval('var clo1 = gen1(42);'); +global.eval('var clo2 = gen1("smoot");'); +var scripts1 = evalAndNoteScripts('f(clo1)'); +var scripts2 = evalAndNoteScripts('f(clo2)'); +assertEq(scripts1.argument, scripts2.argument); + +// Different closures made from the same 'function' declaration should yield +// the same script. +global.eval('function gen2(x) { function clo(y) { return x+y; }; return clo; }'); +global.eval('var clo1 = gen2(42);'); +global.eval('var clo2 = gen2("smoot");'); +var scripts1 = evalAndNoteScripts('f(clo1)'); +var scripts2 = evalAndNoteScripts('f(clo2)'); +assertEq(scripts1.argument, scripts2.argument); + +// Different closures made from the same 'function' statement should yield +// the same script. +global.eval('function gen3(x) { if (true) { function clo(y) { return x+y; }; return clo; } }'); +global.eval('var clo1 = gen3(42);'); +global.eval('var clo2 = gen3("smoot");'); +var scripts1 = evalAndNoteScripts('f(clo1)'); +var scripts2 = evalAndNoteScripts('f(clo2)'); +assertEq(scripts1.argument, scripts2.argument); diff --git a/js/src/jit-test/tests/debug/Script-02.js b/js/src/jit-test/tests/debug/Script-02.js new file mode 100644 index 000000000..071dc465a --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-02.js @@ -0,0 +1,6 @@ +// Debugger.Script throws when applied as a constructor. + +load(libdir + 'asserts.js'); + +assertThrowsInstanceOf(function() { Debugger.Script(); }, TypeError); +assertThrowsInstanceOf(function() { new Debugger.Script(); }, TypeError); diff --git a/js/src/jit-test/tests/debug/Script-clearBreakpoint-01.js b/js/src/jit-test/tests/debug/Script-clearBreakpoint-01.js new file mode 100644 index 000000000..4d0b2bb83 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-clearBreakpoint-01.js @@ -0,0 +1,19 @@ +// A breakpoint handler may clear itself. + +var g = newGlobal(); +var bphits = 0; +var handler = {hit: function (frame) { frame.script.clearBreakpoint(this); bphits++; }}; +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var offs = frame.script.getLineOffsets(g.line0 + 3); + for (var i = 0; i < offs.length; i++) + frame.script.setBreakpoint(offs[i], handler); + hits++; +}; +g.eval("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "for (var i = 0; i < 4; i++)\n" + // line0 + 2 + " result = 'ok';\n"); // line0 + 3 +assertEq(hits, 1); +assertEq(bphits, 1); diff --git a/js/src/jit-test/tests/debug/Script-clearBreakpoint-02.js b/js/src/jit-test/tests/debug/Script-clearBreakpoint-02.js new file mode 100644 index 000000000..3fefb98c0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-clearBreakpoint-02.js @@ -0,0 +1,26 @@ +// A breakpoint cleared during dispatch does not fire. +// (Breakpoint dispatch is well-behaved even when breakpoint handlers clear other breakpoints.) + +var g = newGlobal(); +var dbg = Debugger(g); +var log = ''; +dbg.onDebuggerStatement = function (frame) { + var s = frame.script; + function handler(i) { + if (i === 1) + return function () { log += i; s.clearBreakpoint(h[1]); s.clearBreakpoint(h[2]); }; + return function () { log += i; }; + } + var offs = s.getLineOffsets(g.line0 + 2); + var h = []; + for (var i = 0; i < 4; i++) { + h[i] = {hit: handler(i)}; + for (var j = 0; j < offs.length; j++) + s.setBreakpoint(offs[j], h[i]); + } +}; + +g.eval("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "result = 'ok';\n"); // line0 + 2 +assertEq(log, '013'); diff --git a/js/src/jit-test/tests/debug/Script-clearBreakpoint-03.js b/js/src/jit-test/tests/debug/Script-clearBreakpoint-03.js new file mode 100644 index 000000000..34fdc91ba --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-clearBreakpoint-03.js @@ -0,0 +1,25 @@ +// Clearing a breakpoint by handler can clear multiple breakpoints. + +var g = newGlobal(); +var dbg = Debugger(g); +var s; +dbg.onDebuggerStatement = function (frame) { + s = frame.eval("f").return.script; +}; +g.eval("var line0 = Error().lineNumber;\n" + + "function f(a, b) {\n" + // line0 + 1 + " return a + b;\n" + // line0 + 2 + "}\n" + + "debugger;\n"); + +var hits = 0; +var handler = {hit: function (frame) { hits++; s.clearBreakpoint(handler); }}; +var offs = s.getLineOffsets(g.line0 + 2); +for (var i = 0; i < 4; i++) { + for (var j = 0; j < offs.length; j++) + s.setBreakpoint(offs[j], handler); +} + +assertEq(g.f(2, 2), 4); +assertEq(hits, 1); +assertEq(s.getBreakpoints().length, 0); diff --git a/js/src/jit-test/tests/debug/Script-clearBreakpoint-04.js b/js/src/jit-test/tests/debug/Script-clearBreakpoint-04.js new file mode 100644 index 000000000..6ddb95f03 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-clearBreakpoint-04.js @@ -0,0 +1,28 @@ +// clearBreakpoint clears breakpoints for the current Debugger object only. + +var g = newGlobal(); + +var hits = 0; +var handler = { + hit: function (frame) { + hits++; + frame.script.clearBreakpoint(handler); + } +}; + +function attach(i) { + var dbg = Debugger(g); + dbg.onDebuggerStatement = function (frame) { + var s = frame.script; + var offs = s.getLineOffsets(g.line0 + 2); + for (var i = 0; i < offs.length; i++) + s.setBreakpoint(offs[i], handler); + }; +} +for (var i = 0; i < 4; i++) + attach(i); + +g.eval("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "Math.sin(0);\n"); // line0 + 2 +assertEq(hits, 4); diff --git a/js/src/jit-test/tests/debug/Script-displayName-01.js b/js/src/jit-test/tests/debug/Script-displayName-01.js new file mode 100644 index 000000000..1f1000d73 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-displayName-01.js @@ -0,0 +1,17 @@ +// Debugger.Script.prototype.displayName + +var g = newGlobal(); +var dbg = Debugger(g); +var name; +dbg.onDebuggerStatement = f => { name = f.callee.script.displayName; }; + +g.eval("(function f() { debugger; })();"); +assertEq(name, "f"); +g.eval("(function () { debugger; })();"); +assertEq(name, undefined); +g.eval("Function('debugger;')();"); +assertEq(name, "anonymous"); +g.eval("var f = function() { debugger; }; f()"); +assertEq(name, "f"); +g.eval("var a = {}; a.f = function() { debugger; }; a.f()"); +assertEq(name, "a.f"); diff --git a/js/src/jit-test/tests/debug/Script-format-01.js b/js/src/jit-test/tests/debug/Script-format-01.js new file mode 100644 index 000000000..008de4e62 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-format-01.js @@ -0,0 +1,19 @@ +// Tests that JavaScript scripts have a "js" format and wasm scripts have a +// "wasm" format. + +var g = newGlobal(); +var dbg = new Debugger(g); + +var gotScript; +dbg.onNewScript = (script) => { + gotScript = script; +}; + +g.eval(`(() => {})()`); +assertEq(gotScript.format, "js"); + +if (!wasmIsSupported()) + quit(); + +g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" 0))')));`); +assertEq(gotScript.format, "wasm"); diff --git a/js/src/jit-test/tests/debug/Script-gc-01.js b/js/src/jit-test/tests/debug/Script-gc-01.js new file mode 100644 index 000000000..5d71b1e39 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-gc-01.js @@ -0,0 +1,26 @@ +// Debugger.Script instances with live referents stay alive. + +var N = 4; +var g = newGlobal(); +var dbg = new Debugger(g); +var i; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.script instanceof Debugger.Script, true); + frame.script.id = i; +}; + +g.eval('var arr = [];') +for (i = 0; i < N; i++) // loop to defeat conservative GC + g.eval("arr.push(function () { debugger }); arr[arr.length - 1]();"); + +gc(); + +var hits; +dbg.onDebuggerStatement = function (frame) { + hits++; + assertEq(frame.script.id, i); +}; +hits = 0; +for (i = 0; i < N; i++) + g.arr[i](); +assertEq(hits, N); diff --git a/js/src/jit-test/tests/debug/Script-gc-02.js b/js/src/jit-test/tests/debug/Script-gc-02.js new file mode 100644 index 000000000..33d33dfc1 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-gc-02.js @@ -0,0 +1,14 @@ +// Debugger.Scripts keep their referents alive. + +var g = newGlobal(); +var dbg = Debugger(g); +var arr = []; +dbg.onDebuggerStatement = function (frame) { arr.push(frame.script); }; +g.eval("for (var i = 0; i < 10; i++) Function('debugger;')();"); +assertEq(arr.length, 10); + +gc(); + +for (var i = 0; i < arr.length; i++) + assertEq(arr[i].lineCount, 1); + diff --git a/js/src/jit-test/tests/debug/Script-gc-03.js b/js/src/jit-test/tests/debug/Script-gc-03.js new file mode 100644 index 000000000..b2cb70232 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-gc-03.js @@ -0,0 +1,15 @@ +// Referents of Debugger.Scripts in other compartments always survive per-compartment GC. + +var g = newGlobal(); +var dbg = Debugger(g); +var arr = []; +dbg.onDebuggerStatement = function (frame) { arr.push(frame.script); }; +g.eval("for (var i = 0; i < 100; i++) Function('debugger;')();"); +assertEq(arr.length, 100); + +gc(g); + +for (var i = 0; i < arr.length; i++) + assertEq(arr[i].lineCount, 1); + +gc(); diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-01.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-01.js new file mode 100644 index 000000000..620d56244 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-01.js @@ -0,0 +1,19 @@ +// getColumnOffsets correctly places the various parts of a ForStatement. + +var global = newGlobal(); +Debugger(global).onDebuggerStatement = function (frame) { + var script = frame.eval("f").return.script; + script.getAllColumnOffsets().forEach(function (offset) { + script.setBreakpoint(offset.offset, { + hit: function (frame) { + assertEq(offset.lineNumber, 1); + global.log += offset.columnNumber + " "; + } + }); + }); +}; + +global.log = ''; +global.eval("function f(n) { for (var i = 0; i < n; ++i) log += '. '; log += '! '; } debugger;"); +global.f(3); +assertEq(global.log, "25 32 44 . 39 32 44 . 39 32 44 . 39 32 57 ! 70 "); diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-02.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-02.js new file mode 100644 index 000000000..405b13d62 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-02.js @@ -0,0 +1,21 @@ +// getColumnOffsets correctly places multiple variable declarations. + +var global = newGlobal(); +Debugger(global).onDebuggerStatement = function (frame) { + var script = frame.eval("f").return.script; + script.getAllColumnOffsets().forEach(function (offset) { + script.setBreakpoint(offset.offset, { + hit: function (frame) { + assertEq(offset.lineNumber, 1); + global.log += offset.columnNumber + " "; + } + }); + }); +}; + +global.log = ''; +global.eval("function f(n){var w0,x1=3,y2=4,z3=9} debugger;"); +global.f(3); + +// Should have hit each variable declared. +assertEq(global.log, "21 26 31 35 "); diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-03.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-03.js new file mode 100644 index 000000000..1019f0cdf --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-03.js @@ -0,0 +1,20 @@ +// getColumnOffsets correctly places comma separated expressions. + +var global = newGlobal(); +Debugger(global).onDebuggerStatement = function (frame) { + var script = frame.eval("f").return.script; + script.getAllColumnOffsets().forEach(function (offset) { + script.setBreakpoint(offset.offset, { + hit: function (frame) { + assertEq(offset.lineNumber, 1); + global.log += offset.columnNumber + " "; + } + }); + }); +}; + +global.log = ''; +global.eval("function f(n){print(n),print(n),print(n)} debugger;"); +global.f(3); +// Should hit each call that was separated by commas. +assertEq(global.log, "14 23 32 40 "); diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-04.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-04.js new file mode 100644 index 000000000..23a537f53 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-04.js @@ -0,0 +1,20 @@ +// getColumnOffsets correctly places object properties. + +var global = newGlobal(); +Debugger(global).onDebuggerStatement = function (frame) { + var script = frame.eval("f").return.script; + script.getAllColumnOffsets().forEach(function (offset) { + script.setBreakpoint(offset.offset, { + hit: function (frame) { + assertEq(offset.lineNumber, 1); + global.log += offset.columnNumber + " "; + } + }); + }); +}; + +global.log = ''; +global.eval("function f(n){var o={a:1,b:2,c:3}} debugger;"); +global.f(3); +// Should hit each property in the object. +assertEq(global.log, "18 21 25 29 33 "); diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-05.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-05.js new file mode 100644 index 000000000..0e1704ee2 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-05.js @@ -0,0 +1,20 @@ +// getColumnOffsets correctly places array properties. + +var global = newGlobal(); +Debugger(global).onDebuggerStatement = function (frame) { + var script = frame.eval("f").return.script; + script.getAllColumnOffsets().forEach(function (offset) { + script.setBreakpoint(offset.offset, { + hit: function (frame) { + assertEq(offset.lineNumber, 1); + global.log += offset.columnNumber + " "; + } + }); + }); +}; + +global.log = ''; +global.eval("function f(n){var a=[1,2,n]} debugger;"); +global.f(3); +// Should hit each item in the array. +assertEq(global.log, "18 21 23 25 27 "); diff --git a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-06.js b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-06.js new file mode 100644 index 000000000..20b49add8 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-06.js @@ -0,0 +1,28 @@ +// getColumnOffsets correctly places function calls. + +var global = newGlobal(); +Debugger(global).onDebuggerStatement = function (frame) { + var script = frame.eval("f").return.script; + script.getAllColumnOffsets().forEach(function (offset) { + script.setBreakpoint(offset.offset, { + hit: function (frame) { + assertEq(offset.lineNumber, 1); + global.log += offset.columnNumber + " "; + } + }); + }); +}; + +global.log = ""; +global.eval("function ppppp() { return 1; }"); +// 1 2 3 4 +// 01234567890123456789012345678901234567890123456789 +global.eval("function f(){ 1 && ppppp(ppppp()) && new Error() } debugger;"); +global.f(); + +// 14 - Enter the function body +// 25 - Inner print() +// 19 - Outer print() +// 37 - new Error() +// 49 - Exit the function body +assertEq(global.log, "14 25 19 37 49 "); diff --git a/js/src/jit-test/tests/debug/Script-getBreakpoints-01.js b/js/src/jit-test/tests/debug/Script-getBreakpoints-01.js new file mode 100644 index 000000000..1d31d9972 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getBreakpoints-01.js @@ -0,0 +1,40 @@ +// Basic Script.prototype.getBreakpoints tests. + +var g = newGlobal(); +g.eval("var line0 = Error().lineNumber;\n" + + "function f(x) {\n" + // line0 + 1 + " if (x < 0)\n" + // line0 + 2 + " return -x;\n" + // line0 + 3 + " return x;\n" + + "}"); + +var s; +var offsets = []; +var handlers = []; +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + s = frame.eval("f").return.script; + var off; + + for (var i = 0; i < 3; i++) { + var off = s.getLineOffsets(g.line0 + 2 + i)[0]; + assertEq(typeof off, 'number'); + handlers[i] = {}; + s.setBreakpoint(off, handlers[i]); + offsets[i] = off; + } +}; +g.eval("debugger;"); + +// getBreakpoints without an offset gets all breakpoints in the script. +var bps = s.getBreakpoints(); +assertEq(bps.length, handlers.length); +for (var i = 0; i < bps.length; i++) + assertEq(bps.indexOf(handlers[i]) !== -1, true); + +// getBreakpoints with an offset finds only breakpoints at that offset. +for (var i = 0; i < offsets.length; i++) { + var bps = s.getBreakpoints(offsets[i]); + assertEq(bps.length, 1); + assertEq(bps[0], handlers[i]); +} diff --git a/js/src/jit-test/tests/debug/Script-getBreakpoints-02.js b/js/src/jit-test/tests/debug/Script-getBreakpoints-02.js new file mode 100644 index 000000000..183fe1180 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getBreakpoints-02.js @@ -0,0 +1,42 @@ +// Script.prototype.getBreakpoints returns breakpoints for the current Debugger object only. + +var g = newGlobal(); + +var debuggers = []; +var hits = 0; +function attach(g, i) { + var dbg = Debugger(g); + dbg.onDebuggerStatement = function (frame) { + var s = frame.script; + var offs = s.getLineOffsets(g.line0 + 2); + var hitAny = false; + var handler = { + hit: function (frame) { + assertEq(this, handler); + assertEq(frame.script, s); + var bps = s.getBreakpoints(); + assertEq(bps.length, offs.length); + for (var i = 0; i < bps.length; i++) + assertEq(bps[i], handler); + + if (!hitAny) { + hitAny = true; + hits++; + } + } + }; + for (var i = 0; i < offs.length; i++) + s.setBreakpoint(offs[i], handler); + hits++; + }; + debuggers[i] = dbg; +} + +for (var i = 0; i < 3; i++) + attach(g, i); +g.done = false; +g.eval("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "done = true;\n"); // line0 + 2 +assertEq(hits, 6); +assertEq(g.done, true); diff --git a/js/src/jit-test/tests/debug/Script-getChildScripts-01.js b/js/src/jit-test/tests/debug/Script-getChildScripts-01.js new file mode 100644 index 000000000..e0b245a85 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getChildScripts-01.js @@ -0,0 +1,42 @@ +// Basic getChildScripts tests. + +var g = newGlobal(); +var dbg = new Debugger(g); +var log; +function note(s) { + assertEq(s instanceof Debugger.Script, true); + log += 'S'; + var c = s.getChildScripts(); + if (c.length > 0) { + log += '['; + for (var i = 0; i < c.length; i++) + note(c[i]); + log += ']'; + } +} +dbg.onDebuggerStatement = function (frame) { note(frame.script); }; + +function test(code, expected) { + log = ''; + g.eval(code); + assertEq(log, expected); +} + +test("debugger;", + "S"); +test("function f() {} debugger;", + "S[S]"); +test("function g() { function h() { function k() {} return k; } return h; } debugger;", + "S[S[S[S]]]"); +test("function q() {} function qq() {} debugger;", + "S[SS]"); +test("[0].map(function id(a) { return a; }); debugger;", + "S[S]"); +test("Function('return 2+2;')(); debugger;", + "S"); +test("var obj = {get x() { return 0; }, set x(v) {}}; debugger;", + "S[SS]"); +test("function r(n) { for (var i = 0; i < n; i++) yield i; } debugger;", + "S[S]"); +test("function* qux(n) { for (var i = 0; i < n; i++) yield i; } debugger;", + "S[S]"); diff --git a/js/src/jit-test/tests/debug/Script-getChildScripts-02.js b/js/src/jit-test/tests/debug/Script-getChildScripts-02.js new file mode 100644 index 000000000..2a6c1b9ca --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getChildScripts-02.js @@ -0,0 +1,20 @@ +// getChildScripts returns scripts in source order. + +var g = newGlobal(); +var dbg = new Debugger(g); +var scripts = []; +var cs; +dbg.onDebuggerStatement = function (frame) { + scripts.push(frame.script); + if (scripts.length === 1) + cs = frame.script.getChildScripts(); +}; + +g.eval("function f() { debugger; }\n" + + "var g = function () { debugger; }\n" + + "debugger; f(); g();"); + +assertEq(scripts.length, 3); +assertEq(cs.length, 2); +assertEq(cs[0], scripts[1]); +assertEq(cs[1], scripts[2]); diff --git a/js/src/jit-test/tests/debug/Script-getChildScripts-03.js b/js/src/jit-test/tests/debug/Script-getChildScripts-03.js new file mode 100644 index 000000000..2e29a7c86 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getChildScripts-03.js @@ -0,0 +1,16 @@ +// getChildScripts on a direct eval script returns the right scripts. +// (A bug had it also returning the script for the calling function.) + +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var arr = frame.script.getChildScripts(); + assertEq(arr.length, 1); + assertEq(arr[0], frame.eval("h").return.script); + hits++; +}; + +g.eval("function f(s) { eval(s); }"); +g.f("debugger; function h(a) { return a + 1; }"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Script-getChildScripts-04.js b/js/src/jit-test/tests/debug/Script-getChildScripts-04.js new file mode 100644 index 000000000..ab2a979d5 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getChildScripts-04.js @@ -0,0 +1,15 @@ +// script.getChildScripts() works correctly during the newScript hook. +// (A bug had it including the script for the calling function.) + +var g = newGlobal(); +g.eval("function h(a) { eval(a); }"); + +var dbg = Debugger(g); +var arr, kscript; +dbg.onNewScript = function (script) { arr = script.getChildScripts(); }; +dbg.onDebuggerStatement = function (frame) { kscript = frame.callee.script; }; + +g.h("function k(a) { debugger; return a + 1; } k(-1);"); +assertEq(kscript instanceof Debugger.Script, true); +assertEq(arr.length, 1); +assertEq(arr[0], kscript); diff --git a/js/src/jit-test/tests/debug/Script-getChildScripts-05.js b/js/src/jit-test/tests/debug/Script-getChildScripts-05.js new file mode 100644 index 000000000..ffdfcd0e2 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getChildScripts-05.js @@ -0,0 +1,16 @@ +// Test that lazy inner functions inside eval are tagged properly so we don't +// incorrectly do NAME -> GNAME optimization. + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onNewScript = function delazify(script, global) { + // Force delazification of inner functions. + script.getChildScripts(); +}; + +g.eval("" + function f() { + var $; + eval('var obj={foo:1}; $=function() { assertEq(obj.foo, 1); }'); + return $; +}); +g.eval("f()();"); diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-01.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-01.js new file mode 100644 index 000000000..ae1d8c35b --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-01.js @@ -0,0 +1,13 @@ +// getLineOffsets on a line that is definitely outside a script returns an empty array. + +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var offs = frame.script.getLineOffsets(g.line0 + 2); + assertEq(Array.isArray(offs), true); + assertEq(offs.length, 0); + hits++; +}; +g.eval("var line0 = Error().lineNumber; debugger;"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-02.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-02.js new file mode 100644 index 000000000..c77691af2 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-02.js @@ -0,0 +1,33 @@ +// getLineOffsets correctly places the various parts of a ForStatement. + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + function handler(line) { + return {hit: function (frame) { g.log += "" + line; }}; + } + + var s = frame.eval("f").return.script; + for (var line = 2; line <= 6; line++) { + var offs = s.getLineOffsets(g.line0 + line); + var h = handler(line); + for (var i = 0; i < offs.length; i++) { + assertEq(s.getOffsetLocation(offs[i]).lineNumber, g.line0 + line); + s.setBreakpoint(offs[i], h); + } + } +}; + +g.log = ''; +g.eval("var line0 = Error().lineNumber;\n" + + "function f(n) {\n" + // line0 + 1 + " for (var i = 0;\n" + // line0 + 2 + " i < n;\n" + // line0 + 3 + " i++)\n" + // line0 + 4 + " log += '.';\n" + // line0 + 5 + " log += '!';\n" + // line0 + 6 + "}\n" + + "debugger;\n"); +assertEq(g.log, ""); +g.f(3); +assertEq(g.log, "235.435.435.436!"); diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-03.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-03.js new file mode 100644 index 000000000..d6e3f37cd --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-03.js @@ -0,0 +1,36 @@ +// getLineOffsets treats one-line compound statements as having only one entry-point. +// (A breakpoint on a line that only executes once will only hit once.) + +var g = newGlobal(); +g.line0 = null; +var dbg = Debugger(g); +var log; +dbg.onDebuggerStatement = function (frame) { + var s = frame.script; + var lineno = g.line0 + 2; + var offs = s.getLineOffsets(lineno); + for (var i = 0; i < offs.length; i++) { + assertEq(s.getOffsetLocation(offs[i]).lineNumber, lineno); + s.setBreakpoint(offs[i], {hit: function () { log += 'B'; }}); + } + log += 'A'; +}; + +function test(s) { + log = ''; + g.eval("line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + s); // line0 + 2 + assertEq(log, 'AB'); +} + +g.i = 0; +g.j = 0; +test("{i++; i--; i++; i--; }"); +test("if (i === 0) i++; else i--;"); +test("while (i < 5) i++;"); +test("do --i; while (i > 0);"); +test("for (i = 0; i < 5; i++) j++;"); +test("for (var x in [0, 1, 2]) j++;"); +test("switch (i) { case 0: j = 0; case 1: j = 1; case 2: j = 2; default: j = i; }"); +test("switch (i) { case 'A': j = 0; case 'B': j = 1; case 'C': j = 2; default: j = i; }"); diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-04.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-04.js new file mode 100644 index 000000000..8fee27913 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-04.js @@ -0,0 +1,53 @@ +// getLineOffsets works with instructions reachable only by breaking out of a loop or switch. + +var g = newGlobal(); +g.line0 = null; +var dbg = Debugger(g); +var where; +dbg.onDebuggerStatement = function (frame) { + var s = frame.eval("f").return.script; + var lineno = g.line0 + where; + var offs = s.getLineOffsets(lineno); + for (var i = 0; i < offs.length; i++) { + assertEq(s.getOffsetLocation(offs[i]).lineNumber, lineno); + s.setBreakpoint(offs[i], {hit: function () { g.log += 'B'; }}); + } + g.log += 'A'; +}; + +function test(s) { + var count = (s.split(/\n/).length - 1); // number of newlines in s + g.log = ''; + where = 3 + count + 1; + g.eval("line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "function f(i) {\n" + // line0 + 2 + s + // line0 + 3 ... line0 + where - 2 + " log += '?';\n" + // line0 + where - 1 + " log += '!';\n" + // line0 + where + "}\n"); + g.f(0); + assertEq(g.log, 'A?B!'); +} + +test("i = 128;\n" + + "for (;;) {\n" + + " var x = i - 10;;\n" + + " if (x < 0)\n" + + " break;\n" + + " i >>= 2;\n" + + "}\n"); + +test("while (true)\n" + + " if (++i === 2) break;\n"); + +test("do {\n" + + " if (++i === 2) break;\n" + + "} while (true);\n"); + +test("switch (i) {\n" + + " case 2: return 7;\n" + + " case 1: return 8;\n" + + " case 0: break;\n" + + " default: return -i;\n" + + "}\n"); diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-05.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-05.js new file mode 100644 index 000000000..f2f65a232 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-05.js @@ -0,0 +1,65 @@ +// getLineOffsets identifies multiple ways to land on a line. + +var g = newGlobal(); +g.line0 = null; +var dbg = Debugger(g); +var where; +dbg.onDebuggerStatement = function (frame) { + var s = frame.script, lineno, offs; + + lineno = g.line0 + where; + offs = s.getLineOffsets(lineno); + for (var i = 0; i < offs.length; i++) { + assertEq(s.getOffsetLocation(offs[i]).lineNumber, lineno); + s.setBreakpoint(offs[i], {hit: function () { g.log += 'B'; }}); + } + + lineno++; + offs = s.getLineOffsets(lineno); + for (var i = 0; i < offs.length; i++) { + assertEq(s.getOffsetLocation(offs[i]).lineNumber, lineno); + s.setBreakpoint(offs[i], {hit: function () { g.log += 'C'; }}); + } + + g.log += 'A'; +}; + +function test(s) { + assertEq(s.charAt(s.length - 1), '\n'); + var count = (s.split(/\n/).length - 1); // number of lines in s + g.log = ''; + where = 1 + count; + g.eval("line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + s + // line0 + 2 ... line0 + where + "log += 'D';\n"); + assertEq(g.log, 'AB!CD'); +} + +// if-statement with yes and no paths on a single line +g.i = 0; +test("if (i === 0)\n" + + " log += '!'; else log += 'X';\n"); +test("if (i === 2)\n" + + " log += 'X'; else log += '!';\n"); + +// break to a line that has code inside and outside the loop +g.i = 2; +test("while (1) {\n" + + " if (i === 2) break;\n" + + " log += 'X'; } log += '!';\n"); + +// leaving a while loop by failing the test, when the last line has stuff both inside and outside the loop +g.i = 0; +test("while (i > 0) {\n" + + " if (i === 70) log += 'X';\n" + + " --i; } log += '!';\n"); + +// multiple case-labels on the same line +g.i = 0; +test("switch (i) {\n" + + " case 0: case 1: log += '!'; break; }\n"); +test("switch ('' + i) {\n" + + " case '0': case '1': log += '!'; break; }\n"); +test("switch (i) {\n" + + " case 'ok' + i: case i - i: log += '!'; break; }\n"); diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-06.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-06.js new file mode 100644 index 000000000..da312ccf9 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-06.js @@ -0,0 +1,99 @@ +// getLineOffsets works with extended instructions, such as JSOP_GOTOX. + +var g = newGlobal(); +g.line0 = null; +var dbg = Debugger(g); +var where; +dbg.onDebuggerStatement = function (frame) { + var s = frame.script; + var offs; + var lineno = g.line0 + where; + offs = s.getLineOffsets(lineno); + for (var i = 0; i < offs.length; i++) { + assertEq(s.getOffsetLocation(offs[i]).lineNumber, lineno); + s.setBreakpoint(offs[i], {hit: function (frame) { g.log += 'B'; }}); + } + g.log += 'A'; +}; + +function test(s) { + assertEq(s.charAt(s.length - 1) !== '\n', true); + var count = s.split(/\n/).length; // number of lines in s + g.i = 0; + g.log = ''; + where = 1 + count; + g.eval("line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + s + // line0 + 2 ... line0 + where + " log += 'C';\n"); + assertEq(g.log, 'ABC'); +} + +function repeat(s) { + return Array((1 << 14) + 1).join(s); // 16K copies of s +} +var long_expr = "i" + repeat(" + i"); +var long_throw_stmt = "throw " + long_expr + ";\n"; + +// long break (JSOP_GOTOX) +test("for (;;) {\n" + + " if (i === 0)\n" + + " break;\n" + + " " + long_throw_stmt + + "}"); + +// long continue (JSOP_GOTOX) +test("do {\n" + + " if (i === 0)\n" + + " continue;\n" + + " " + long_throw_stmt + + "} while (i !== 0);"); + +// long if consequent (JSOP_IFEQX) +test("if (i === 2) {\n" + + " " + long_throw_stmt + + "}"); + +// long catch-block with finally (JSOP_GOSUBX) +test("try {\n" + + " i = 0;\n" + + "} catch (exc) {\n" + + " throw " + long_expr + ";\n" + + "} finally {\n" + + " i = 1;\n" + + "}"); + +// long case (JSOP_TABLESWITCHX) +test("switch (i) {\n" + + " default:\n" + + " case 1: " + long_throw_stmt + + " case 0: i++; }"); + +test("switch (i) {\n" + + " case 1: case 2: case 3: " + long_throw_stmt + + " default: i++; }"); + +// long case (JSOP_LOOKUPSWITCHX) +test("switch ('' + i) {\n" + + " default:\n" + + " case '1': " + long_throw_stmt + + " case '0': i++; }"); + +test("switch (i) {\n" + + " case '1': case '2': case '3': " + long_throw_stmt + + " default: i++; }"); + +// long case or case-expression (JSOP_CASEX) +test("switch (i) {\n" + + " case i + 1 - i:\n" + + " default:\n" + + " " + long_throw_stmt + + " case i + i:\n" + + " i++; break; }"); + +// long case when JSOP_CASE is used (JSOP_DEFAULTX) +test("switch (i) {\n" + + " case i + 1 - i:\n" + + " " + long_throw_stmt + + " default:\n" + + " i++; break; }"); diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-07.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-07.js new file mode 100644 index 000000000..726a38e5d --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-07.js @@ -0,0 +1,19 @@ +// Lazy scripts should correctly report line offsets + +var g = newGlobal(); +var dbg = new Debugger(); + +g.eval("// Header comment\n" + // <- line 6 in this file + "\n" + + "\n" + + "function f(n) {\n" + // <- line 9 in this file + " var foo = '!';\n" + + "}"); + +dbg.addDebuggee(g); +var scripts = dbg.findScripts(); +for (var i = 0; i < scripts.length; i++) { + // Nothing should have offsets for the deffun on line 9 if lazy scripts + // correctly update the position. + assertEq(scripts[i].getLineOffsets(9).length, 0); +} diff --git a/js/src/jit-test/tests/debug/Script-getLineOffsets-08.js b/js/src/jit-test/tests/debug/Script-getLineOffsets-08.js new file mode 100644 index 000000000..8a7fb69e1 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-08.js @@ -0,0 +1,25 @@ +// A "while" or a "for" loop should have a single entry point. + +var g = newGlobal(); +var dbg = new Debugger(g); + +dbg.onDebuggerStatement = function(frame) { + var s = frame.eval('f').return.script; + + // There should be just a single entry point for the first line of + // the function. See below to understand the "+2". + assertEq(s.getLineOffsets(g.line0 + 2).length, 1); +}; + + +function test(code) { + g.eval('var line0 = Error().lineNumber;\n' + + 'function f() {\n' + // line0 + 1 + code + '\n' + // line0 + 2 -- see above + '}\n' + + 'debugger;'); +} + +test('while (false)\n;'); +test('for (;false;)\n;'); +test('for (;;) break;\n;'); diff --git a/js/src/jit-test/tests/debug/Script-getOffsetLine-01.js b/js/src/jit-test/tests/debug/Script-getOffsetLine-01.js new file mode 100644 index 000000000..58ee76c81 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getOffsetLine-01.js @@ -0,0 +1,25 @@ +// Basic getOffsetLocation test, using Error.lineNumber as the gold standard. + +var g = newGlobal(); +var dbg = Debugger(g); +var hits; +dbg.onDebuggerStatement = function (frame) { + var knownLine = frame.eval("line").return; + assertEq(frame.script.getOffsetLocation(frame.offset).lineNumber, knownLine); + hits++; +}; + +hits = 0; +g.eval("var line = new Error().lineNumber; debugger;"); +assertEq(hits, 1); + +hits = 0; +g.eval("var s = 2 + 2;\n" + + "s += 2;\n" + + "line = new Error().lineNumber; debugger;\n" + + "s += 2;\n" + + "s += 2;\n" + + "line = new Error().lineNumber; debugger;\n" + + "s += 2;\n" + + "assertEq(s, 12);\n"); +assertEq(hits, 2); diff --git a/js/src/jit-test/tests/debug/Script-getOffsetLine-02.js b/js/src/jit-test/tests/debug/Script-getOffsetLine-02.js new file mode 100644 index 000000000..84215fd3b --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getOffsetLine-02.js @@ -0,0 +1,19 @@ +// Frame.script/offset and Script.getOffsetLocation work in non-top frames. + +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + var a = []; + for (; frame.type == "call"; frame = frame.older) + a.push(frame.script.getOffsetLocation(frame.offset).lineNumber - g.line0); + assertEq(a.join(","), "1,2,3,4"); + hits++; +}; +g.eval("var line0 = Error().lineNumber;\n" + + "function f0() { debugger; }\n" + + "function f1() { f0(); }\n" + + "function f2() { f1(); }\n" + + "function f3() { f2(); }\n" + + "f3();\n"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/Script-getOffsetLocation.js b/js/src/jit-test/tests/debug/Script-getOffsetLocation.js new file mode 100644 index 000000000..7cfc2e790 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getOffsetLocation.js @@ -0,0 +1,37 @@ +// getOffsetLocation agrees with getAllColumnOffsets + +var global = newGlobal(); +Debugger(global).onDebuggerStatement = function (frame) { + var script = frame.script; + var byOffset = []; + script.getAllColumnOffsets().forEach(function (entry) { + var {lineNumber, columnNumber, offset} = entry; + byOffset[offset] = {lineNumber, columnNumber}; + }); + + frame.onStep = function() { + var offset = frame.offset; + var location = script.getOffsetLocation(offset); + if (location.isEntryPoint) { + assertEq(location.lineNumber, byOffset[offset].lineNumber); + assertEq(location.columnNumber, byOffset[offset].columnNumber); + } else { + assertEq(byOffset[offset], undefined); + } + }; +}; + +function test(body) { + print("Test: " + body); + global.eval(`function f(n) { debugger; ${body} }`); + global.f(3); +} + +test("for (var i = 0; i < n; ++i) ;"); +test("var w0,x1=3,y2=4,z3=9"); +test("print(n),print(n),print(n)"); +test("var o={a:1,b:2,c:3}"); +test("var a=[1,2,n]"); + +global.eval("function ppppp() { return 1; }"); +test("1 && ppppp(ppppp()) && new Error()"); diff --git a/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-01.js b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-01.js new file mode 100644 index 000000000..130d28439 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-01.js @@ -0,0 +1,475 @@ +// Currently the Jit integration has a few issues, let's keep this test +// case deterministic. +// +// - Baseline OSR increments the loop header twice. +// - Ion is not updating any counter yet. +// +if (getJitCompilerOptions()["ion.warmup.trigger"] != 30) + setJitCompilerOption("ion.warmup.trigger", 30); +if (getJitCompilerOptions()["baseline.warmup.trigger"] != 10) + setJitCompilerOption("baseline.warmup.trigger", 10); + +/* + * These test cases are annotated with the output produced by LCOV [1]. Comment + * starting with //<key> without any spaces are used as a reference for the code + * coverage output. Any "$" in these line comments are replaced by the current + * line number, and any "%" are replaced with the current function name (defined + * by the FN key). + * + * [1] http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php + */ +function checkGetOffsetsCoverage(fun) { + var keys = [ "TN", "SF", "FN", "FNDA", "FNF", "FNH", "BRDA", "BRF", "BRH", "DA", "LF", "LH" ]; + function startsWithKey(s) { + for (k of keys) { + if (s.startsWith(k)) + return true; + } + return false; + }; + + // Extract the body of the function, as the code to be executed. + var source = fun.toSource(); + source = source.slice(source.indexOf('{') + 1, source.lastIndexOf('}')); + + // Extract comment starting with the previous keys, as a reference. + var lcovRef = []; + var currLine = 0; + var currFun = [{name: "top-level", braces: 1}]; + for (var line of source.split('\n')) { + currLine++; + + for (var comment of line.split("//").slice(1)) { + if (!startsWithKey(comment)) + continue; + comment = comment.trim(); + if (comment.startsWith("FN:")) + currFun.push({ name: comment.split(',')[1], braces: 0 }); + var name = currFun[currFun.length - 1].name; + if (!comment.startsWith("DA:")) + continue; + comment = { + offset: null, + lineNumber: currLine, + columnNumber: null, + count: comment.split(",")[1] | 0, + script: (name == "top-level" ? undefined : name) + }; + lcovRef.push(comment); + } + + var deltaBraces = line.split('{').length - line.split('}').length; + currFun[currFun.length - 1].braces += deltaBraces; + if (currFun[currFun.length - 1].braces == 0) + currFun.pop(); + } + + // Create a new global and instrument it with a debugger, to find all scripts, + // created in the current global. + var g = newGlobal(); + var dbg = Debugger(g); + dbg.collectCoverageInfo = true; + + var topLevel = null; + dbg.onNewScript = function (s) { + topLevel = s; + dbg.onNewScript = function () {}; + }; + + // Evaluate the code, and collect the hit counts for each scripts / lines. + g.eval(source); + + var coverageRes = []; + function collectCoverage(s) { + var res = s.getOffsetsCoverage(); + if (res == null) + res = [{ + offset: null, + lineNumber: null, + columnNumber: null, + script: s.displayName, + count: 0 + }]; + else { + res.map(function (e) { + e.script = s.displayName; + return e; + }); + } + coverageRes.push(res); + s.getChildScripts().forEach(collectCoverage); + }; + collectCoverage(topLevel); + coverageRes = [].concat(...coverageRes); + + // Check that all the lines are present the result. + function match(ref) { + return function (entry) { + return ref.lineNumber == entry.lineNumber && ref.script == entry.script; + } + } + function ppObj(entry) { + var str = "{"; + for (var k in entry) { + if (entry[k] != null) + str += " '" + k + "': " + entry[k] + ","; + } + str += "}"; + return str; + } + for (ref of lcovRef) { + var res = coverageRes.find(match(ref)); + if (!res) { + // getOffsetsCoverage returns null if we have no result for the + // script. We added a fake entry with an undefined lineNumber, which is + // used to match against the modified reference. + var missRef = Object.create(ref); + missRef.lineNumber = null; + res = coverageRes.find(match(missRef)); + } + + if (!res || res.count != ref.count) { + print("Cannot find `" + ppObj(ref) + "` in the following results:\n", coverageRes.map(ppObj).join("\n")); + print("In the following source:\n", source); + assertEq(true, false); + } + } +} + +checkGetOffsetsCoverage(function () { //FN:$,top-level + ",".split(','); //DA:$,1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level + function f() { //FN:$,f + ",".split(','); //DA:$,0 + } + ",".split(','); //DA:$,1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level + function f() { //FN:$,f + ",".split(','); //DA:$,1 + } + f(); //DA:$,1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,% + var l = ",".split(','); //DA:$,1 + if (l.length == 3) //DA:$,1 + l.push(''); //DA:$,0 + l.pop(); //DA:$,1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,% + var l = ",".split(','); //DA:$,1 + if (l.length == 2) //DA:$,1 + l.push(''); //DA:$,1 + l.pop(); //DA:$,1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level + var l = ",".split(','); //DA:$,1 + if (l.length == 3) //DA:$,1 + l.push(''); //DA:$,0 + else + l.pop(); //DA:$,1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level + var l = ",".split(','); //DA:$,1 + if (l.length == 2) //DA:$,1 + l.push(''); //DA:$,1 + else + l.pop(); //DA:$,0 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level + var l = ",".split(','); //DA:$,1 + if (l.length == 2) //DA:$,1 + l.push(''); //DA:$,1 + else { + if (l.length == 1) //DA:$,0 + l.pop(); //DA:$,0 + } +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level + function f(i) { //FN:$,f + var x = 0; //DA:$,2 + while (i--) { // Currently OSR wrongly count the loop header twice. + // So instead of DA:$,12 , we have DA:$,13 . + x += i; //DA:$,10 + x = x / 2; //DA:$,10 + } + return x; //DA:$,2 + } + + f(5); //DA:$,1 + f(5); //DA:$,1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level + try { //DA:$,1 + var l = ",".split(','); //DA:$,1 + if (l.length == 2) { //DA:$,1 + l.push(''); //DA:$,1 + throw l; //DA:$,1 + } + l.pop(); //DA:$,0 + } catch (x) { //DA:$,1 + x.pop(); //DA:$,1 + } +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level + var l = ",".split(','); //DA:$,1 + try { //DA:$,1 + try { //DA:$,1 + if (l.length == 2) { //DA:$,1 + l.push(''); //DA:$,1 + throw l; //DA:$,1 + } + l.pop(); //DA:$,0 + } finally { //DA:$,1 + l.pop(); //DA:$,1 + } + } catch (x) { //DA:$,1 + } +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level + function f() { //FN:$,f + throw 1; //DA:$,1 + f(); //DA:$,0 + } + var l = ",".split(','); //DA:$,1 + try { //DA:$,1 + f(); //DA:$,1 + f(); //DA:$,0 + } catch (x) { //DA:$,1 + } +}); + + +// Test TableSwitch opcode +checkGetOffsetsCoverage(function () { //FN:$,top-level + var l = ",".split(','); //DA:$,1 + switch (l.length) { //DA:$,1 + case 0: + l.push('0'); //DA:$,0 + break; + case 1: + l.push('1'); //DA:$,0 + break; + case 2: + l.push('2'); //DA:$,1 + break; + case 3: + l.push('3'); //DA:$,0 + break; + } + l.pop(); //DA:$,1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level + var l = ",".split(','); //DA:$,1 + switch (l.length) { //DA:$,1 + case 0: + l.push('0'); //DA:$,0 + case 1: + l.push('1'); //DA:$,0 + case 2: + l.push('2'); //DA:$,1 + case 3: + l.push('3'); //DA:$,1 + } + l.pop(); //DA:$,1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level + var l = ",".split(','); //DA:$,1 + switch (l.length) { //DA:$,1 + case 5: + l.push('5'); //DA:$,0 + case 4: + l.push('4'); //DA:$,0 + case 3: + l.push('3'); //DA:$,0 + case 2: + l.push('2'); //DA:$,1 + } + l.pop(); //DA:$,1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level + var l = ",".split(','); //DA:$,1 + switch (l.length) { //DA:$,1 + case 2: + l.push('2'); //DA:$,1 + case 5: + l.push('5'); //DA:$,1 + } + l.pop(); //DA:$,1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level + var l = ",".split(','); //DA:$,1 + switch (l.length) { //DA:$,1 + case 3: + l.push('1'); //DA:$,0 + case 5: + l.push('5'); //DA:$,0 + } + l.pop(); //DA:$,1 +}); + +// Unfortunately the differences between switch implementations leaks in the +// code coverage reports. +checkGetOffsetsCoverage(function () { //FN:$,top-level + function f(a) { //FN:$,f + return a; //DA:$,2 + } + var l = ",".split(','); //DA:$,1 + switch (l.length) { //DA:$,1 + case f(-42): //DA:$,1 + l.push('1'); //DA:$,0 + case f(51): //DA:$,1 + l.push('5'); //DA:$,0 + } + l.pop(); //DA:$,1 +}); + + +checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,% + var l = ",".split(','); //DA:$,1 + switch (l.length) { //DA:$,1 //BRDA:$,0,0,- //BRDA:$,0,1,1 //BRDA:$,0,2,- //BRDA:$,0,3,- + case 0: + case 1: + l.push('0'); //DA:$,0 + l.push('1'); //DA:$,0 + case 2: + l.push('2'); //DA:$,1 + case 3: + l.push('3'); //DA:$,1 + } + l.pop(); //DA:$,1 + //FNF:1 + //FNH:1 + //LF:7 + //LH:5 + //BRF:4 + //BRH:1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,% + var l = ",".split(','); //DA:$,1 + switch (l.length) { //DA:$,1 //BRDA:$,0,0,- //BRDA:$,0,1,- //BRDA:$,0,2,1 //BRDA:$,0,3,- + case 0: + l.push('0'); //DA:$,0 + case 1: + l.push('1'); //DA:$,0 + case 2: + case 3: + l.push('2'); //DA:$,1 + l.push('3'); //DA:$,1 + } + l.pop(); //DA:$,1 + //FNF:1 + //FNH:1 + //LF:7 + //LH:5 + //BRF:4 + //BRH:1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,% + var l = ",".split(','); //DA:$,1 + switch (l.length) { //DA:$,1 //BRDA:$,0,0,- //BRDA:$,0,1,- //BRDA:$,0,2,1 //BRDA:$,0,3,- + case 0: + l.push('0'); //DA:$,0 + case 1: + default: + l.push('1'); //DA:$,0 + case 2: + l.push('2'); //DA:$,1 + case 3: + l.push('3'); //DA:$,1 + } + l.pop(); //DA:$,1 + //FNF:1 + //FNH:1 + //LF:7 + //LH:5 + //BRF:4 + //BRH:1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,% + var l = ",".split(','); //DA:$,1 + switch (l.length) { //DA:$,1 //BRDA:$,0,0,- //BRDA:$,0,1,- //BRDA:$,0,2,1 //BRDA:$,0,3,- + case 0: + l.push('0'); //DA:$,0 + case 1: + l.push('1'); //DA:$,0 + default: + case 2: + l.push('2'); //DA:$,1 + case 3: + l.push('3'); //DA:$,1 + } + l.pop(); //DA:$,1 + //FNF:1 + //FNH:1 + //LF:7 + //LH:5 + //BRF:4 + //BRH:1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,% + var l = ",".split(','); //DA:$,1 + switch (l.length) { //DA:$,1 //BRDA:$,0,0,- //BRDA:$,0,1,- //BRDA:$,0,2,1 //BRDA:$,0,3,- //BRDA:$,0,4,- + case 0: + l.push('0'); //DA:$,0 + case 1: + l.push('1'); //DA:$,0 + default: + l.push('default'); //DA:$,0 + case 2: + l.push('2'); //DA:$,1 + case 3: + l.push('3'); //DA:$,1 + } + l.pop(); //DA:$,1 + //FNF:1 + //FNH:1 + //LF:8 + //LH:5 + //BRF:5 + //BRH:1 +}); + +checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,% + var l = ",".split(','); //DA:$,1 + switch (l.length) { //DA:$,1 //BRDA:$,0,0,- //BRDA:$,0,1,- //BRDA:$,0,2,- //BRDA:$,0,3,1 + case 0: + l.push('0'); //DA:$,0 + case 1: + l.push('1'); //DA:$,0 + default: + l.push('2'); //DA:$,1 + case 3: + l.push('3'); //DA:$,1 + } + l.pop(); //DA:$,1 + //FNF:1 + //FNH:1 + //LF:7 + //LH:5 + //BRF:4 + //BRH:1 +}); + +// If you add a test case here, do the same in +// jit-test/tests/coverage/simple.js diff --git a/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-02.js b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-02.js new file mode 100644 index 000000000..20c8f6b8d --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-02.js @@ -0,0 +1,41 @@ +// |jit-test| --ion-pgo=off; + +// This script check that when we enable / disable the code coverage collection, +// then we have different results for the getOffsetsCoverage methods. + +var g = newGlobal(); +var dbg = Debugger(g); +var coverageInfo = []; +var num = 20; +function loop(i) { + var n = 0; + for (n = 0; n < i; n++) + debugger; +} +g.eval(loop.toSource()); + +dbg.onDebuggerStatement = function (f) { + // Collect coverage info each time we hit a debugger statement. + coverageInfo.push(f.callee.script.getOffsetsCoverage()); +}; + +coverageInfo = []; +dbg.collectCoverageInfo = false; +g.eval("loop(" + num + ");"); +assertEq(coverageInfo.length, num); +assertEq(coverageInfo[0], null); +assertEq(coverageInfo[num - 1], null); + +coverageInfo = []; +dbg.collectCoverageInfo = true; +g.eval("loop(" + num + ");"); +assertEq(coverageInfo.length, num); +assertEq(!coverageInfo[0], false); +assertEq(!coverageInfo[num - 1], false); + +coverageInfo = []; +dbg.collectCoverageInfo = false; +g.eval("loop(" + num + ");"); +assertEq(coverageInfo.length, num); +assertEq(coverageInfo[0], null); +assertEq(coverageInfo[num - 1], null); diff --git a/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-03.js b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-03.js new file mode 100644 index 000000000..094f60447 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-03.js @@ -0,0 +1,21 @@ +// |jit-test| error: Error: can't start debugging: a debuggee script is on the stack + +var g = newGlobal(); +var dbg = Debugger(g); +function loop(i) { + var n = 0; + for (n = 0; n < i; n++) + debugger; +} +g.eval(loop.toSource()); + +var countDown = 20; +dbg.onDebuggerStatement = function (f) { + // Should throw an error. + if (countDown > 0 && --countDown == 0) { + dbg.collectCoverageInfo = !dbg.collectCoverageInfo; + } +}; + +dbg.collectCoverageInfo = false; +g.eval("loop("+ (2 * countDown) +");"); diff --git a/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-04.js b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-04.js new file mode 100644 index 000000000..e9888ac16 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-04.js @@ -0,0 +1,22 @@ +// |jit-test| error: Error: can't start debugging: a debuggee script is on the stack + +var g = newGlobal(); +var dbg = Debugger(g); + +function loop(i) { + var n = 0; + for (n = 0; n < i; n++) + debugger; +} +g.eval(loop.toSource()); + +var countDown = 20; +dbg.onDebuggerStatement = function (f) { + // Should throw an error. + if (countDown > 0 && --countDown == 0) { + dbg.collectCoverageInfo = !dbg.collectCoverageInfo; + } +}; + +dbg.collectCoverageInfo = true; +g.eval("loop("+ (2 * countDown) +");"); diff --git a/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-05.js b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-05.js new file mode 100644 index 000000000..de2c74351 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-05.js @@ -0,0 +1,24 @@ +var g = newGlobal(); +var dbg = Debugger(g); +function f(x) { + while (x) { + interruptIf(true); + x -= 1; + } +} +g.eval(f.toSource()); + +// Toogle the debugger while the function f is running. +setInterruptCallback(toogleDebugger); +function toogleDebugger() { + dbg.enabled = !dbg.enabled; + return true; +} + +dbg.collectCoverageInfo = false; +dbg.enabled = false; +g.eval("f(10);"); + +dbg.collectCoverageInfo = true; +dbg.enabled = false; +g.eval("f(10);"); diff --git a/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-bug1233178.js b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-bug1233178.js new file mode 100644 index 000000000..633c8176b --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-bug1233178.js @@ -0,0 +1,13 @@ +gczeal(2); +g = newGlobal(); +dbg = Debugger(g); +function loop() { + for (var i = 0; i < 10; i++) + debugger; +} +g.eval(loop.toSource()); +dbg.onDebuggerStatement = function(f) { + f.script.getOffsetsCoverage(); +} +dbg.collectCoverageInfo = true; +g.eval("loop")(); diff --git a/js/src/jit-test/tests/debug/Script-global-01.js b/js/src/jit-test/tests/debug/Script-global-01.js new file mode 100644 index 000000000..bc088a33a --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-global-01.js @@ -0,0 +1,20 @@ +// Debugger.Script.prototype.script returns the global the script runs in. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +var log = ''; +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.script.global, gw); +} + +g.eval('debugger;'); +assertEq(log, 'd'); + +g.eval('function f() { debugger; }'); +g.f(); +assertEq(log, 'dd'); + +assertEq(gw.getOwnPropertyDescriptor('f').value.global, gw); diff --git a/js/src/jit-test/tests/debug/Script-global-02.js b/js/src/jit-test/tests/debug/Script-global-02.js new file mode 100644 index 000000000..fb4f30205 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-global-02.js @@ -0,0 +1,40 @@ +// Debugger.Script.prototype.script returns the global the script runs in. +// Multi-global version. + +var dbg = new Debugger; + +var g1 = newGlobal(); +var g1w = dbg.addDebuggee(g1); + +var g2 = newGlobal(); +var g2w = dbg.addDebuggee(g2); + +var g3 = newGlobal(); +var g3w = dbg.addDebuggee(g3); + +var log = ''; +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.script.global, g1w); + assertEq(frame.older.script.global, g2w); + assertEq(frame.older.older.script.global, g3w); + assertEq(frame.older.older.older.script.global, g1w); +} + +g1.eval('function f() { debugger; }'); + +g2.g1 = g1; +g2.eval('function g() { g1.f(); }'); + +g3.g2 = g2; +g3.eval('function h() { g2.g(); }'); + +g1.g3 = g3; +g1.eval('function i() { g3.h(); }'); + +g1.i(); +assertEq(log, 'd'); + +assertEq(g1w.getOwnPropertyDescriptor('f').value.global, g1w); +assertEq(g2w.getOwnPropertyDescriptor('g').value.global, g2w); +assertEq(g3w.getOwnPropertyDescriptor('h').value.global, g3w); diff --git a/js/src/jit-test/tests/debug/Script-isInCatchScope.js b/js/src/jit-test/tests/debug/Script-isInCatchScope.js new file mode 100644 index 000000000..0aabc5da4 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-isInCatchScope.js @@ -0,0 +1,68 @@ +// Test if isInCatchScope properly detects catch blocks. + +let g = newGlobal(); +let dbg = new Debugger(g); + +function test(string, mustBeCaught) { + let index = 0; + dbg.onExceptionUnwind = function (frame) { + let willBeCaught = false; + do { + if (frame.script.isInCatchScope(frame.offset)) { + willBeCaught = true; + break; + } + frame = frame.older; + } while (frame != null); + assertEq(willBeCaught, mustBeCaught[index++]); + }; + + try { + g.eval(string); + } catch (ex) {} + assertEq(index, mustBeCaught.length); +} + +// Should correctly detect catch blocks +test("throw new Error();", [false]); +test("try { throw new Error(); } catch (e) {}", [true]); +test("try { throw new Error(); } finally {}", [false, false]); +test("try { throw new Error(); } catch (e) {} finally {}", [true]); + +// Source of the exception shouldn't matter +test("(null)();", [false]); +test("try { (null)(); } catch (e) {}", [true]); +test("try { (null)(); } finally {}", [false, false]); +test("try { (null)(); } catch (e) {} finally {}", [true]); + +// Should correctly detect catch blocks in functions +test("function f() { throw new Error(); } f();", [false, false]); +test("function f() { try { throw new Error(); } catch (e) {} } f();", [true]); +test("function f() { try { throw new Error(); } finally {} } f();", [false, false, false]); +test("function f() { try { throw new Error(); } catch (e) {} finally {} } f();", [true]); + +// Should correctly detect catch blocks in evals +test("eval('throw new Error();')", [false, false]); +test("eval('try { throw new Error(); } catch (e) {}');", [true]); +test("eval('try { throw new Error(); } finally {}');", [false, false, false]); +test("eval('try { throw new Error(); } catch (e) {} finally {}');", [true]); + +// Should correctly detect rethrows +test("try { throw new Error(); } catch (e) { throw e; }", [true, false]); +test("try { try { throw new Error(); } catch (e) { throw e; } } catch (e) {}", [true, true]); +test("try { try { throw new Error(); } finally {} } catch (e) {}", [true, true]); +test("function f() { try { throw new Error(); } catch (e) { throw e; } } f();", [true, false, false]); +test("function f() { try { try { throw new Error(); } catch (e) { throw e; } } catch (e) {} } f();", [true, true]); +test("function f() { try { try { throw new Error(); } finally {} } catch (e) {} } f();", [true, true]); +test("eval('try { throw new Error(); } catch (e) { throw e; }')", [true, false, false]); +test("eval('try { try { throw new Error(); } catch (e) { throw e; } } catch (e) {}')", [true, true]); + +// Should correctly detect catch blocks across frame boundaries +test("function f() { throw new Error(); } try { f(); } catch (e) {}", [true, true]); +test("function f() { throw new Error(); } try { f(); } catch (e) { throw e; }", [true, true, false]); +test("try { eval('throw new Error()'); } catch (e) {}", [true, true]); +test("try { eval('throw new Error()'); } catch (e) { throw e; }", [true, true, false]); + +// Should correctly detect catch blocks just before and just after throws +test("throw new Error; try {} catch (e) {}", [false]); +test("try {} catch (e) {} throw new Error();", [false]); diff --git a/js/src/jit-test/tests/debug/Script-lineCount.js b/js/src/jit-test/tests/debug/Script-lineCount.js new file mode 100644 index 000000000..8eff08112 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-lineCount.js @@ -0,0 +1,23 @@ +// Test Script.lineCount. + +var g = newGlobal(); +var dbg = Debugger(g); + +function test(scriptText, expectedLineCount) { + let found = false; + + dbg.onNewScript = function(script, global) { + assertEq(script.lineCount, expectedLineCount); + found = true; + }; + + g.evaluate(scriptText); + assertEq(found, true); +} + +src = 'var a = (function(){\n' + // 0 + 'var b = 9;\n' + // 1 + 'console.log("x", b);\n'+ // 2 + 'return b;\n' + // 3 + '})();\n'; // 4 +test(src, 5); diff --git a/js/src/jit-test/tests/debug/Script-source-01.js b/js/src/jit-test/tests/debug/Script-source-01.js new file mode 100644 index 000000000..2698bd3f6 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-source-01.js @@ -0,0 +1,26 @@ +/* + * Script.prototype.source should be an object. Moreover, it should be the + * same object for each child script within the same debugger. + */ +let g = newGlobal(); +let dbg = new Debugger(g); + +let count = 0; +dbg.onNewScript = function (script) { + assertEq(typeof script.source, "object"); + function traverse(script) { + ++count; + script.getChildScripts().forEach(function (child) { + assertEq(child.source, script.source); + traverse(child); + }); + } + traverse(script); +} + +g.eval("2 * 3"); +g.eval("function f() {}"); +g.eval("function f() { function g() {} }"); +g.eval("eval('2 * 3')"); +g.eval("new Function('2 * 3')"); +assertEq(count, 10); diff --git a/js/src/jit-test/tests/debug/Script-source-02.js b/js/src/jit-test/tests/debug/Script-source-02.js new file mode 100644 index 000000000..77eaaae6d --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-source-02.js @@ -0,0 +1,16 @@ +/* + * Script.prototype.source should be the same object for both the top-level + * script and the script of functions accessed as debuggee values on the global + */ +let g = newGlobal(); +let dbg = new Debugger(); +let gw = dbg.addDebuggee(g); + +let count = 0; +dbg.onDebuggerStatement = function (frame) { + ++count; + assertEq(frame.script.source, gw.makeDebuggeeValue(g.f).script.source); +} + +g.eval("function f() {}; debugger;"); +assertEq(count, 1); diff --git a/js/src/jit-test/tests/debug/Script-source-03.js b/js/src/jit-test/tests/debug/Script-source-03.js new file mode 100644 index 000000000..5e535eb97 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-source-03.js @@ -0,0 +1,22 @@ +/* + * Script.prototype.source should be a different object for the same script + * within different debuggers. + */ +let g = newGlobal(); +let dbg1 = new Debugger(g); +let dbg2 = new Debugger(g); + +var count = 0; +var source; +function test(script) { + ++count; + if (!source) + source = script.source; + else + assertEq(script.source != source, true); +}; +dbg1.onNewScript = test; +dbg2.onNewScript = test; + +g.eval("2 * 3"); +assertEq(count, 2); diff --git a/js/src/jit-test/tests/debug/Script-sourceStart-01.js b/js/src/jit-test/tests/debug/Script-sourceStart-01.js new file mode 100644 index 000000000..dd1cf2374 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-sourceStart-01.js @@ -0,0 +1,22 @@ +/* + * Script.prototype.sourceStart and Script.prototype.sourceLength should both be + * a number. + */ +let g = newGlobal(); +let dbg = new Debugger(g); + +var count = 0; +function test(string, range) { + dbg.onNewScript = function (script) { + ++count; + assertEq(script.sourceStart, range[0]); + assertEq(script.sourceLength, range[1]); + }; + + g.eval(string); +}; + +test("", [0, 0]); +test("2 * 3", [0, 5]); +test("2\n*\n3", [0, 5]); +assertEq(count, 3); diff --git a/js/src/jit-test/tests/debug/Script-sourceStart-02.js b/js/src/jit-test/tests/debug/Script-sourceStart-02.js new file mode 100644 index 000000000..c947a6b0b --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-sourceStart-02.js @@ -0,0 +1,32 @@ +/* + * For function statements, Script.prototype.sourceStart and + * Script.prototype.sourceLength should comprise both the opening '(' and the + * closing '}'. + */ +let g = newGlobal(); +let dbg = new Debugger(g); + +function test(string, ranges) { + var index = 0; + dbg.onNewScript = function (script) { + function traverse(script) { + script.getChildScripts().forEach(function (script) { + assertEq(script.sourceStart, ranges[index][0]); + assertEq(script.sourceLength, ranges[index][1]); + ++index; + traverse(script); + }); + } + traverse(script); + }; + + g.eval(string); + assertEq(index, ranges.length); +}; + +test("function f() {}", [[10, 5]]); +test("function f() { function g() {} }", [[10, 22], [25, 5]]); +test("function f() { function g() { function h() {} } }", [[10, 39], [25, 22], [40, 5]]); +test("function f() { if (true) function g() {} }", [[10, 32], [35, 5]]); +test("var o = { get p () {} }", [[16, 5]]); +test("var o = { set p (x) {} }", [[16, 6]]); diff --git a/js/src/jit-test/tests/debug/Script-sourceStart-03.js b/js/src/jit-test/tests/debug/Script-sourceStart-03.js new file mode 100644 index 000000000..1910dde5a --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-sourceStart-03.js @@ -0,0 +1,35 @@ +/* + * For arrow functions, Script.prototype.sourceStart and + * Script.prototype.sourceLength should comprise the entire function expression + * (including arguments) + */ +let g = newGlobal(); +let dbg = new Debugger(g); + +function test(string, ranges) { + var index = 0; + dbg.onNewScript = function (script) { + function traverse(script) { + script.getChildScripts().forEach(function (script) { + assertEq(script.sourceStart, ranges[index][0]); + assertEq(script.sourceLength, ranges[index][1]); + ++index; + traverse(script); + }); + } + traverse(script); + }; + + g.eval(string); + + /* + * In some configurations certain child scripts are listed twice, so we + * cannot rely on index always having the exact same value + */ + assertEq(0 < index && index <= ranges.length, true); +}; + +test("() => {}", [[0, 8]]); +test("(x, y) => { x * y }", [[0, 19]]); +test("x => x * x", [[0, 10]]); +test("x => x => x * x", [[0, 15], [5, 10], [5, 10]]); diff --git a/js/src/jit-test/tests/debug/Script-sourceStart-04.js b/js/src/jit-test/tests/debug/Script-sourceStart-04.js new file mode 100644 index 000000000..c12e669bf --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-sourceStart-04.js @@ -0,0 +1,25 @@ +/* + * For eval and Function constructors, Script.prototype.sourceStart and + * Script.prototype.sourceLength should comprise the entire script (excluding + * arguments in the case of Function constructors) + */ +let g = newGlobal(); +let dbg = new Debugger(g); + +var count = 0; +function test(string, range) { + dbg.onNewScript = function (script) { + ++count; + if (count % 2 == 0) { + assertEq(script.sourceStart, range[0]); + assertEq(script.sourceLength, range[1]); + } + } + + g.eval(string); +} + +test("eval('2 * 3')", [0, 5]); +test("new Function('2 * 3')", [0, 5]); +test("new Function('x', 'x * x')", [0, 5]); +assertEq(count, 6); diff --git a/js/src/jit-test/tests/debug/Script-startLine.js b/js/src/jit-test/tests/debug/Script-startLine.js new file mode 100644 index 000000000..f5b2d1085 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-startLine.js @@ -0,0 +1,63 @@ +var g = newGlobal(); +var dbg = Debugger(g); +var start, count; +dbg.onDebuggerStatement = function (frame) { + assertEq(start, undefined); + start = frame.script.startLine; + count = frame.script.lineCount; + assertEq(typeof frame.script.url, 'string'); +}; + +function test(f, manualCount) { + start = count = g.first = g.last = undefined; + f(); + if (manualCount) + g.last = g.first + manualCount - 1; + assertEq(start, g.first); + assertEq(count, g.last + 1 - g.first); + print(start, count); +} + +test(function () { + g.eval("first = Error().lineNumber;\n" + + "debugger;\n" + + "last = Error().lineNumber;\n"); +}); + +test(function () { + g.evaluate("first = Error().lineNumber;\n" + + "debugger;\n" + + Array(17000).join("\n") + + "last = Error().lineNumber;\n"); +}); + +test(function () { + g.eval("function f1() { first = Error().lineNumber\n" + + " debugger;\n" + + " last = Error().lineNumber; }\n" + + "f1();"); +}); + +g.eval("function f2() {\n" + + " eval('first = Error().lineNumber\\n\\ndebugger;\\n\\nlast = Error().lineNumber;');\n" + + "}\n"); +test(g.f2); +test(g.f2); + +// Having a last = Error().lineNumber forces a setline srcnote, so test a +// function that ends with newline srcnotes. +g.eval("/* Any copyright is dedicated to the Public Domain.\n" + + " http://creativecommons.org/publicdomain/zero/1.0/ */\n" + + "\n" + + "function secondCall() { first = Error().lineNumber;\n" + + " debugger;\n" + + " // Comment\n" + + " eval(\"42;\");\n" + + " function foo() {}\n" + + " if (true) {\n" + + " foo();\n" + + // The "missing" newline here is a trick to make a newline + // source note come at the end. A real newline between the two + // closing braces causes a setline note instead. + " } }"); +test(g.secondCall, 8); diff --git a/js/src/jit-test/tests/debug/Script-url.js b/js/src/jit-test/tests/debug/Script-url.js new file mode 100644 index 000000000..71f9dc629 --- /dev/null +++ b/js/src/jit-test/tests/debug/Script-url.js @@ -0,0 +1,10 @@ +// Script.prototype.url can be a string or null. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +for (var fileName of ['file:///var/foo.js', null]) { + g.evaluate("function f(x) { return 2*x; }", {fileName: fileName}); + var fw = gw.getOwnPropertyDescriptor('f').value; + assertEq(fw.script.url, fileName); +} diff --git a/js/src/jit-test/tests/debug/Source-displayURL-deprecated.js b/js/src/jit-test/tests/debug/Source-displayURL-deprecated.js new file mode 100644 index 000000000..f8017d7f7 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-displayURL-deprecated.js @@ -0,0 +1,26 @@ +/* -*- js-indent-level: 4; indent-tabs-mode: nil -*- */ +// Source.prototype.displayURL can be a string or null. + +let g = newGlobal('new-compartment'); +let dbg = new Debugger; +let gw = dbg.addDebuggee(g); + +function getDisplayURL() { + let fw = gw.makeDebuggeeValue(g.f); + return fw.script.source.displayURL; +} + +// Comment pragmas +g.evaluate('function f() {}\n' + + '//@ sourceURL=file:///var/quux.js'); +assertEq(getDisplayURL(), 'file:///var/quux.js'); + +g.evaluate('function f() {}\n' + + '/*//@ sourceURL=file:///var/quux.js*/'); +assertEq(getDisplayURL(), 'file:///var/quux.js'); + +g.evaluate('function f() {}\n' + + '/*\n' + + '//@ sourceURL=file:///var/quux.js\n' + + '*/'); +assertEq(getDisplayURL(), 'file:///var/quux.js'); diff --git a/js/src/jit-test/tests/debug/Source-displayURL.js b/js/src/jit-test/tests/debug/Source-displayURL.js new file mode 100644 index 000000000..a16a76186 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-displayURL.js @@ -0,0 +1,91 @@ +/* -*- js-indent-level: 4; indent-tabs-mode: nil -*- */ +// Source.prototype.displayURL can be a string or null. + +let g = newGlobal('new-compartment'); +let dbg = new Debugger; +let gw = dbg.addDebuggee(g); + +function getDisplayURL() { + let fw = gw.makeDebuggeeValue(g.f); + return fw.script.source.displayURL; +} + +// Without a source url +g.evaluate("function f(x) { return 2*x; }"); +assertEq(getDisplayURL(), null); + +// With a source url +g.evaluate("function f(x) { return 2*x; }", {displayURL: 'file:///var/foo.js'}); +assertEq(getDisplayURL(), 'file:///var/foo.js'); + +// Nested functions +let fired = false; +dbg.onDebuggerStatement = function (frame) { + fired = true; + assertEq(frame.script.source.displayURL, 'file:///var/bar.js'); +}; +g.evaluate('(function () { (function () { debugger; })(); })();', + {displayURL: 'file:///var/bar.js'}); +assertEq(fired, true); + +// Comment pragmas +g.evaluate('function f() {}\n' + + '//# sourceURL=file:///var/quux.js'); +assertEq(getDisplayURL(), 'file:///var/quux.js'); + +g.evaluate('function f() {}\n' + + '/*//# sourceURL=file:///var/quux.js*/'); +assertEq(getDisplayURL(), 'file:///var/quux.js'); + +g.evaluate('function f() {}\n' + + '/*\n' + + '//# sourceURL=file:///var/quux.js\n' + + '*/'); +assertEq(getDisplayURL(), 'file:///var/quux.js'); + +// Spaces are disallowed by the URL spec (they should have been +// percent-encoded). +g.evaluate('function f() {}\n' + + '//# sourceURL=http://example.com/has illegal spaces'); +assertEq(getDisplayURL(), 'http://example.com/has'); + +// When the URL is missing, we don't set the sourceMapURL and we don't skip the +// next line of input. +g.evaluate('function f() {}\n' + + '//# sourceURL=\n' + + 'function z() {}'); +assertEq(getDisplayURL(), null); +assertEq('z' in g, true); + +// The last comment pragma we see should be the one which sets the displayURL. +g.evaluate('function f() {}\n' + + '//# sourceURL=http://example.com/foo.js\n' + + '//# sourceURL=http://example.com/bar.js'); +assertEq(getDisplayURL(), 'http://example.com/bar.js'); + +// With both a comment and the evaluate option. +g.evaluate('function f() {}\n' + + '//# sourceURL=http://example.com/foo.js', + {displayURL: 'http://example.com/bar.js'}); +assertEq(getDisplayURL(), 'http://example.com/foo.js'); + + +// Bug 981987 reported that we hadn't set sourceURL yet when firing onNewScript +// from the Function constructor. +var capturedScript; +var capturedDisplayURL; +var capturedSourceMapURL; +dbg.onNewScript = function (script) { + capturedScript = script; + capturedDisplayURL = script.source.displayURL; + capturedSourceMapURL = script.source.sourceMapURL; + dbg.onNewScript = undefined; +}; +var fun = gw.makeDebuggeeValue(g.Function('//# sourceURL=munge.js\n//# sourceMappingURL=grunge.map\n')); +assertEq(capturedScript, fun.script); + +assertEq(capturedDisplayURL, fun.script.source.displayURL); +assertEq(capturedDisplayURL, 'munge.js'); + +assertEq(capturedSourceMapURL, fun.script.source.sourceMapURL); +assertEq(capturedSourceMapURL, 'grunge.map'); diff --git a/js/src/jit-test/tests/debug/Source-element-01.js b/js/src/jit-test/tests/debug/Source-element-01.js new file mode 100644 index 000000000..70c76bef0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-element-01.js @@ -0,0 +1,13 @@ +// Source.prototype.element can be an object or undefined. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +g.evaluate("function f(x) { return 2*x; }", {element: { foo: "bar" }}); +var fw = gw.getOwnPropertyDescriptor('f').value; +assertEq(typeof fw.script.source.element, "object"); +assertEq(fw.script.source.element instanceof Debugger.Object, true); +assertEq(fw.script.source.element.getOwnPropertyDescriptor("foo").value, "bar"); +g.evaluate("function f(x) { return 2*x; }"); +var fw = gw.getOwnPropertyDescriptor('f').value; +assertEq(typeof fw.script.source.element, "undefined"); diff --git a/js/src/jit-test/tests/debug/Source-element-02.js b/js/src/jit-test/tests/debug/Source-element-02.js new file mode 100644 index 000000000..38c92280b --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-element-02.js @@ -0,0 +1,6 @@ +// Specifying an owning element in a cross-global evaluation shouldn't crash. +// That is, when 'evaluate' switches compartments, it should properly wrap +// the CompileOptions members that will become cross-compartment +// references. + +evaluate('42 + 1729', { global: newGlobal(), element: {} }); diff --git a/js/src/jit-test/tests/debug/Source-element-03.js b/js/src/jit-test/tests/debug/Source-element-03.js new file mode 100644 index 000000000..7b192fe1b --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-element-03.js @@ -0,0 +1,27 @@ +// Owning elements and attribute names are attached to scripts compiled +// off-thread. + +if (helperThreadCount() === 0) + quit(0); + +var g = newGlobal(); +var dbg = new Debugger; +var gDO = dbg.addDebuggee(g); + +var elt = new g.Object; +var eltDO = gDO.makeDebuggeeValue(elt); + +var log = ''; +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + var source = frame.script.source; + assertEq(source.element, eltDO); + assertEq(source.elementAttributeName, 'mass'); +}; + +g.offThreadCompileScript('debugger;', + { element: elt, + elementAttributeName: 'mass' }); +log += 'o'; +g.runOffThreadScript(); +assertEq(log, 'od'); diff --git a/js/src/jit-test/tests/debug/Source-elementAttributeName.js b/js/src/jit-test/tests/debug/Source-elementAttributeName.js new file mode 100644 index 000000000..685d2f5eb --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-elementAttributeName.js @@ -0,0 +1,11 @@ +// Source.prototype.elementAttributeName can be a string or undefined. + +var g = newGlobal('new-compartment'); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +g.evaluate("function f(x) { return 2*x; }", {elementAttributeName: "src"}); +var fw = gw.getOwnPropertyDescriptor('f').value; +assertEq(fw.script.source.elementAttributeName, "src"); +g.evaluate("function f(x) { return 2*x; }"); +var fw = gw.getOwnPropertyDescriptor('f').value; +assertEq(fw.script.source.elementAttributeName, undefined); diff --git a/js/src/jit-test/tests/debug/Source-introductionScript-01.js b/js/src/jit-test/tests/debug/Source-introductionScript-01.js new file mode 100644 index 000000000..e61936cc4 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-introductionScript-01.js @@ -0,0 +1,118 @@ +// Dynamically generated sources should have their introduction script and +// offset set correctly. + +var g = newGlobal(); +var dbg = new Debugger; +var gDO = dbg.addDebuggee(g); +var log; + +// Direct eval, while the frame is live. +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + var source = frame.script.source; + var introducer = frame.older; + assertEq(source.introductionScript, introducer.script); + assertEq(source.introductionOffset, introducer.offset); +}; +log = ''; +g.eval('\n\neval("\\n\\ndebugger;");'); +assertEq(log, 'd'); + +// Direct eval, after the frame has been popped. +var introducer, introduced; +dbg.onDebuggerStatement = function (frame) { + log += 'de1'; + introducer = frame.script; + dbg.onDebuggerStatement = function (frame) { + log += 'de2'; + introduced = frame.script.source; + }; +}; +log = ''; +g.evaluate('debugger; eval("\\n\\ndebugger;");', { lineNumber: 1812 }); +assertEq(log, 'de1de2'); +assertEq(introduced.introductionScript, introducer); +assertEq(introducer.getOffsetLocation(introduced.introductionOffset).lineNumber, 1812); + +// Indirect eval, while the frame is live. +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + var source = frame.script.source; + var introducer = frame.older; + assertEq(source.introductionScript, introducer.script); + assertEq(source.introductionOffset, introducer.offset); +}; +log = ''; +g.eval('\n\n(0,eval)("\\n\\ndebugger;");'); +assertEq(log, 'd'); + +// Indirect eval, after the frame has been popped. +var introducer, introduced; +dbg.onDebuggerStatement = function (frame) { + log += 'de1'; + introducer = frame.script; + dbg.onDebuggerStatement = function (frame) { + log += 'de2'; + introduced = frame.script.source; + }; +}; +log = ''; +g.evaluate('debugger; (0,eval)("\\n\\ndebugger;");', { lineNumber: 1066 }); +assertEq(log, 'de1de2'); +assertEq(introduced.introductionScript, introducer); +assertEq(introducer.getOffsetLocation(introduced.introductionOffset).lineNumber, 1066); + +// Function constructor. +dbg.onDebuggerStatement = function (frame) { + log += 'o'; + var outerScript = frame.script; + var outerOffset = frame.offset; + dbg.onDebuggerStatement = function (frame) { + log += 'i'; + var source = frame.script.source; + assertEq(source.introductionScript, outerScript); + assertEq(outerScript.getOffsetLocation(source.introductionOffset).lineNumber, + outerScript.getOffsetLocation(outerOffset).lineNumber); + }; +}; +log = ''; +g.eval('\n\n\ndebugger; Function("debugger;")()'); +assertEq(log, 'oi'); + +// Function constructor, after the the introduction call's frame has been +// popped. +var introducer; +dbg.onDebuggerStatement = function (frame) { + log += 'F2'; + introducer = frame.script; +}; +log = ''; +var fDO = gDO.executeInGlobal('debugger; Function("origami;")', { lineNumber: 1685 }).return; +var source = fDO.script.source; +assertEq(log, 'F2'); +assertEq(source.introductionScript, introducer); +assertEq(introducer.getOffsetLocation(source.introductionOffset).lineNumber, 1685); + +// If the introduction script is in a different global from the script it +// introduced, we don't record it. +dbg.onDebuggerStatement = function (frame) { + log += 'x'; + var source = frame.script.source; + assertEq(source.introductionScript, undefined); + assertEq(source.introductionOffset, undefined); +}; +log = ''; +g.eval('debugger;'); // introduction script is this top-level script +assertEq(log, 'x'); + +// If the code is introduced by a function that doesn't provide +// introduction information, that shouldn't be a problem. +dbg.onDebuggerStatement = function (frame) { + log += 'x'; + var source = frame.script.source; + assertEq(source.introductionScript, undefined); + assertEq(source.introductionOffset, undefined); +}; +log = ''; +g.eval('evaluate("debugger;", { lineNumber: 1729 });'); +assertEq(log, 'x'); diff --git a/js/src/jit-test/tests/debug/Source-introductionScript-02.js b/js/src/jit-test/tests/debug/Source-introductionScript-02.js new file mode 100644 index 000000000..0bc8324f0 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-introductionScript-02.js @@ -0,0 +1,44 @@ +// Calls to 'eval', etc. by JS primitives get attributed to the point of +// the primitive's call. + +var g = newGlobal(); +var dbg = new Debugger; +var gDO = dbg.addDebuggee(g); +var log = ''; + +function outerHandler(frame) { + log += 'o'; + var outerScript = frame.script; + + dbg.onDebuggerStatement = function (frame) { + log += 'i'; + var source = frame.script.source; + var introScript = source.introductionScript; + assertEq(introScript, outerScript); + assertEq(introScript.getOffsetLocation(source.introductionOffset).lineNumber, 1234); + }; +}; + +log = ''; +dbg.onDebuggerStatement = outerHandler; +g.evaluate('debugger; ["debugger;"].map(eval)', { lineNumber: 1234 }); +assertEq(log, 'oi'); + +log = ''; +dbg.onDebuggerStatement = outerHandler; +g.evaluate('debugger; "debugger;".replace(/.*/, eval);', + { lineNumber: 1234 }); +assertEq(log, 'oi'); + + +// If the call takes place in another global, however, we don't record the +// introduction script. +log = ''; +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.script.source.introductionScript, undefined); + assertEq(frame.script.source.introductionOffset, undefined); +}; +["debugger;"].map(g.eval); +"debugger;".replace(/.*/, g.eval); +assertEq(log, 'dd'); diff --git a/js/src/jit-test/tests/debug/Source-introductionScript-03.js b/js/src/jit-test/tests/debug/Source-introductionScript-03.js new file mode 100644 index 000000000..96a07b565 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-introductionScript-03.js @@ -0,0 +1,32 @@ +// We don't record introduction scripts in a different global from the +// introduced script, even if they're both debuggees. + +var dbg = new Debugger; + +var g1 = newGlobal(); +g1.g1 = g1; +var g1DO = dbg.addDebuggee(g1); + +var g2 = newGlobal(); +g2.g1 = g1; + +var log = ''; +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.script.source.introductionScript, undefined); + assertEq(frame.script.source.introductionOffset, undefined); +}; + +g2.eval('g1.eval("debugger;");'); +assertEq(log, 'd'); + +// Just for sanity: when it's not cross-global, we do note the introducer. +log = ''; +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.script.source.introductionScript instanceof Debugger.Script, true); + assertEq(typeof frame.script.source.introductionOffset, "number"); +}; +// Exactly as above, but with g1 instead of g2. +g1.eval('g1.eval("debugger;");'); +assertEq(log, 'd'); diff --git a/js/src/jit-test/tests/debug/Source-introductionScript-04.js b/js/src/jit-test/tests/debug/Source-introductionScript-04.js new file mode 100644 index 000000000..6f0721b63 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-introductionScript-04.js @@ -0,0 +1,8 @@ +// Function.prototype's script source should be fully initialized. +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +var DOfp = gw.getOwnPropertyDescriptor('Function').value.proto; +// This should not crash. +print(DOfp.script.source.introductionScript); diff --git a/js/src/jit-test/tests/debug/Source-introductionType-data b/js/src/jit-test/tests/debug/Source-introductionType-data new file mode 100644 index 000000000..eab746921 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-introductionType-data @@ -0,0 +1 @@ +debugger; diff --git a/js/src/jit-test/tests/debug/Source-introductionType.js b/js/src/jit-test/tests/debug/Source-introductionType.js new file mode 100644 index 000000000..448b784c2 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-introductionType.js @@ -0,0 +1,120 @@ +// Check that scripts' introduction types are properly marked. + +if (helperThreadCount() === 0) + quit(0); + +var g = newGlobal(); +var dbg = new Debugger(); +var gDO = dbg.addDebuggee(g); +var log; + +// (Indirect) eval. +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.script.source.introductionType, 'eval'); +}; +log = ''; +g.eval('debugger;'); +assertEq(log, 'd'); + +// Function constructor. +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.script.source.introductionType, 'Function'); +}; +log = ''; +g.Function('debugger;')(); +assertEq(log, 'd'); + +// GeneratorFunction constructor. +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.script.source.introductionType, 'GeneratorFunction'); +}; +log = ''; +g.eval('(function*() {})').constructor('debugger;')().next(); +assertEq(log, 'd'); + +// Shell 'evaluate' function +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.script.source.introductionType, "js shell evaluate"); +}; +log = ''; +g.evaluate('debugger;'); +assertEq(log, 'd'); + +// Shell 'load' function +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.script.source.introductionType, "js shell load"); +}; +log = ''; +g.load(scriptdir + 'Source-introductionType-data'); +assertEq(log, 'd'); + +// Shell 'run' function +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.script.source.introductionType, "js shell run"); +}; +log = ''; +g.run(scriptdir + 'Source-introductionType-data'); +assertEq(log, 'd'); + +// Shell 'offThreadCompileScript' function. +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.script.source.introductionType, "js shell offThreadCompileScript"); +}; +log = ''; +g.offThreadCompileScript('debugger;'); +g.runOffThreadScript(); +assertEq(log, 'd'); + +// Debugger.Frame.prototype.eval +dbg.onDebuggerStatement = function (frame) { + log += 'o'; + dbg.onDebuggerStatement = innerHandler; + frame.eval('debugger'); + function innerHandler(frame) { + log += 'i'; + assertEq(frame.script.source.introductionType, "debugger eval"); + } +}; +log = ''; +g.eval('debugger;'); +assertEq(log, 'oi'); + +// Debugger.Frame.prototype.evalWithBindings +dbg.onDebuggerStatement = function (frame) { + log += 'o'; + dbg.onDebuggerStatement = innerHandler; + frame.evalWithBindings('debugger', { x: 42 }); + function innerHandler(frame) { + log += 'i'; + assertEq(frame.script.source.introductionType, "debugger eval"); + } +}; +log = ''; +g.eval('debugger;'); +assertEq(log, 'oi'); + +// Debugger.Object.executeInGlobal +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.script.source.introductionType, "debugger eval"); +}; +log = ''; +gDO.executeInGlobal('debugger;'); +assertEq(log, 'd'); + +// Debugger.Object.executeInGlobalWithBindings +dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.script.source.introductionType, "debugger eval"); +}; +log = ''; +gDO.executeInGlobalWithBindings('debugger;', { x: 42 }); +assertEq(log, 'd'); + diff --git a/js/src/jit-test/tests/debug/Source-invisible.js b/js/src/jit-test/tests/debug/Source-invisible.js new file mode 100644 index 000000000..277c32459 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-invisible.js @@ -0,0 +1,12 @@ +// Looking at ScriptSourceObjects in invisible-to-debugger compartments is okay. + +var gi = newGlobal({ invisibleToDebugger: true }); +gi.eval('function f() {}'); + +var gv = newGlobal(); +gv.f = gi.f; +gv.eval('f = clone(f);'); + +var dbg = new Debugger; +var gvw = dbg.addDebuggee(gv); +gvw.getOwnPropertyDescriptor('f').value.script.source; diff --git a/js/src/jit-test/tests/debug/Source-sourceMapURL-deprecated.js b/js/src/jit-test/tests/debug/Source-sourceMapURL-deprecated.js new file mode 100644 index 000000000..f3445e101 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-sourceMapURL-deprecated.js @@ -0,0 +1,82 @@ +// Source.prototype.sourceMapURL can be a string or null. + +let g = newGlobal(); +let dbg = new Debugger; +let gw = dbg.addDebuggee(g); + +function getSourceMapURL() { + let fw = gw.makeDebuggeeValue(g.f); + return fw.script.source.sourceMapURL; +} + +function setSourceMapURL(url) { + let fw = gw.makeDebuggeeValue(g.f); + fw.script.source.sourceMapURL = url; +} + +// Without a source map +g.evaluate("function f(x) { return 2*x; }"); +assertEq(getSourceMapURL(), null); + +// With a source map +g.evaluate("function f(x) { return 2*x; }", {sourceMapURL: 'file:///var/foo.js.map'}); +assertEq(getSourceMapURL(), 'file:///var/foo.js.map'); + +// Nested functions +let fired = false; +dbg.onDebuggerStatement = function (frame) { + fired = true; + assertEq(frame.script.source.sourceMapURL, 'file:///var/bar.js.map'); +}; +g.evaluate('(function () { (function () { debugger; })(); })();', + {sourceMapURL: 'file:///var/bar.js.map'}); +assertEq(fired, true); + +// Comment pragmas +g.evaluate('function f() {}\n' + + '//@ sourceMappingURL=file:///var/quux.js.map'); +assertEq(getSourceMapURL(), 'file:///var/quux.js.map'); + +g.evaluate('function f() {}\n' + + '/*//@ sourceMappingURL=file:///var/quux.js.map*/'); +assertEq(getSourceMapURL(), 'file:///var/quux.js.map'); + +g.evaluate('function f() {}\n' + + '/*\n' + + '//@ sourceMappingURL=file:///var/quux.js.map\n' + + '*/'); +assertEq(getSourceMapURL(), 'file:///var/quux.js.map'); + +// Spaces are disallowed by the URL spec (they should have been +// percent-encoded). +g.evaluate('function f() {}\n' + + '//@ sourceMappingURL=http://example.com/has illegal spaces.map'); +assertEq(getSourceMapURL(), 'http://example.com/has'); + +// When the URL is missing, we don't set the sourceMapURL and we don't skip the +// next line of input. +g.evaluate('function f() {}\n' + + '//@ sourceMappingURL=\n' + + 'function z() {}'); +assertEq(getSourceMapURL(), null); +assertEq('z' in g, true); + +// The last comment pragma we see should be the one which sets the source map's +// URL. +g.evaluate('function f() {}\n' + + '//@ sourceMappingURL=http://example.com/foo.js.map\n' + + '//@ sourceMappingURL=http://example.com/bar.js.map'); +assertEq(getSourceMapURL(), 'http://example.com/bar.js.map'); + +// With both a comment and the evaluate option. +g.evaluate('function f() {}\n' + + '//@ sourceMappingURL=http://example.com/foo.js.map', + {sourceMapURL: 'http://example.com/bar.js.map'}); +assertEq(getSourceMapURL(), 'http://example.com/foo.js.map'); + +// Make sure setting the sourceMapURL manually works +setSourceMapURL('baz.js.map'); +assertEq(getSourceMapURL(), 'baz.js.map'); + +setSourceMapURL(''); +assertEq(getSourceMapURL(), 'baz.js.map'); diff --git a/js/src/jit-test/tests/debug/Source-sourceMapURL.js b/js/src/jit-test/tests/debug/Source-sourceMapURL.js new file mode 100644 index 000000000..3bfe0aa97 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-sourceMapURL.js @@ -0,0 +1,82 @@ +// Source.prototype.sourceMapURL can be a string or null. + +let g = newGlobal(); +let dbg = new Debugger; +let gw = dbg.addDebuggee(g); + +function getSourceMapURL() { + let fw = gw.makeDebuggeeValue(g.f); + return fw.script.source.sourceMapURL; +} + +function setSourceMapURL(url) { + let fw = gw.makeDebuggeeValue(g.f); + fw.script.source.sourceMapURL = url; +} + +// Without a source map +g.evaluate("function f(x) { return 2*x; }"); +assertEq(getSourceMapURL(), null); + +// With a source map +g.evaluate("function f(x) { return 2*x; }", {sourceMapURL: 'file:///var/foo.js.map'}); +assertEq(getSourceMapURL(), 'file:///var/foo.js.map'); + +// Nested functions +let fired = false; +dbg.onDebuggerStatement = function (frame) { + fired = true; + assertEq(frame.script.source.sourceMapURL, 'file:///var/bar.js.map'); +}; +g.evaluate('(function () { (function () { debugger; })(); })();', + {sourceMapURL: 'file:///var/bar.js.map'}); +assertEq(fired, true); + +// Comment pragmas +g.evaluate('function f() {}\n' + + '//# sourceMappingURL=file:///var/quux.js.map'); +assertEq(getSourceMapURL(), 'file:///var/quux.js.map'); + +g.evaluate('function f() {}\n' + + '/*//# sourceMappingURL=file:///var/quux.js.map*/'); +assertEq(getSourceMapURL(), 'file:///var/quux.js.map'); + +g.evaluate('function f() {}\n' + + '/*\n' + + '//# sourceMappingURL=file:///var/quux.js.map\n' + + '*/'); +assertEq(getSourceMapURL(), 'file:///var/quux.js.map'); + +// Spaces are disallowed by the URL spec (they should have been +// percent-encoded). +g.evaluate('function f() {}\n' + + '//# sourceMappingURL=http://example.com/has illegal spaces.map'); +assertEq(getSourceMapURL(), 'http://example.com/has'); + +// When the URL is missing, we don't set the sourceMapURL and we don't skip the +// next line of input. +g.evaluate('function f() {}\n' + + '//# sourceMappingURL=\n' + + 'function z() {}'); +assertEq(getSourceMapURL(), null); +assertEq('z' in g, true); + +// The last comment pragma we see should be the one which sets the source map's +// URL. +g.evaluate('function f() {}\n' + + '//# sourceMappingURL=http://example.com/foo.js.map\n' + + '//# sourceMappingURL=http://example.com/bar.js.map'); +assertEq(getSourceMapURL(), 'http://example.com/bar.js.map'); + +// With both a comment and the evaluate option. +g.evaluate('function f() {}\n' + + '//# sourceMappingURL=http://example.com/foo.js.map', + {sourceMapURL: 'http://example.com/bar.js.map'}); +assertEq(getSourceMapURL(), 'http://example.com/foo.js.map'); + +// Make sure setting the sourceMapURL manually works +setSourceMapURL('baz.js.map'); +assertEq(getSourceMapURL(), 'baz.js.map'); + +setSourceMapURL(''); +assertEq(getSourceMapURL(), 'baz.js.map'); diff --git a/js/src/jit-test/tests/debug/Source-surfaces.js b/js/src/jit-test/tests/debug/Source-surfaces.js new file mode 100644 index 000000000..f2b3e81d9 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-surfaces.js @@ -0,0 +1,33 @@ +// Debugger.Source.prototype + +load(libdir + 'asserts.js'); + +assertThrowsInstanceOf(function () { + Debugger.Source.prototype.text.call(42) +}, TypeError); +assertThrowsInstanceOf(function () { + Debugger.Source.prototype.text.call({}) +}, TypeError); +assertThrowsInstanceOf(function () { + Debugger.Source.prototype.text.call(Debugger.Source.prototype) +}, TypeError); + +assertThrowsInstanceOf(function () { + Debugger.Source.prototype.element.call(42) +}, TypeError); +assertThrowsInstanceOf(function () { + Debugger.Source.prototype.element.call({}) +}, TypeError); +assertThrowsInstanceOf(function () { + Debugger.Source.prototype.element.call(Debugger.Source.prototype) +}, TypeError); + +assertThrowsInstanceOf(function () { + Debugger.Source.prototype.elementAttributeName.call(42) +}, TypeError); +assertThrowsInstanceOf(function () { + Debugger.Source.prototype.elementAttributeName.call({}) +}, TypeError); +assertThrowsInstanceOf(function () { + Debugger.Source.prototype.elementAttributeName.call(Debugger.Source.prototype) +}, TypeError); diff --git a/js/src/jit-test/tests/debug/Source-text-01.js b/js/src/jit-test/tests/debug/Source-text-01.js new file mode 100644 index 000000000..b147d7103 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-text-01.js @@ -0,0 +1,26 @@ +/* + * Debugger.Source.prototype.text should return a string. Moreover, it + * should be the same string for each child script sharing that + * Debugger.Source. + */ +let g = newGlobal(); +let dbg = new Debugger(g); + +var count = 0; +dbg.onNewScript = function (script) { + var text = script.source.text; + assertEq(typeof text, "string"); + function traverse(script) { + ++count; + script.getChildScripts().forEach(function (script) { + assertEq(script.source.text, text); + traverse(script); + }); + }; + traverse(script); +} + +g.eval("2 * 3"); +g.eval("function f() {}"); +g.eval("function f() { function g() {} }"); +assertEq(count, 6); diff --git a/js/src/jit-test/tests/debug/Source-text-02.js b/js/src/jit-test/tests/debug/Source-text-02.js new file mode 100644 index 000000000..64cfce92a --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-text-02.js @@ -0,0 +1,19 @@ +// Nested compilation units (say, an eval with in an eval) should have the +// correct sources attributed to them. +let g = newGlobal(); +let dbg = new Debugger(g); + +var count = 0; +dbg.onNewScript = function (script) { + ++count; + if (count % 2 == 0) + assertEq(script.source.text, text); +} + +g.eval("eval('" + (text = "") + "')"); +g.eval("eval('" + (text = "2 * 3") + "')"); +g.eval("new Function('" + (text = "") + "')"); +g.eval("new Function('" + (text = "2 * 3") + "')"); +evaluate("", { global: g }); +evaluate("2 * 3", { global: g }); +assertEq(count, 10); diff --git a/js/src/jit-test/tests/debug/Source-text-lazy.js b/js/src/jit-test/tests/debug/Source-text-lazy.js new file mode 100644 index 000000000..21896b212 --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-text-lazy.js @@ -0,0 +1,39 @@ +/* + * Debugger.Source.prototype.text should correctly retrieve the source for + * code compiled with CompileOptions::LAZY_SOURCE. + */ + +// withSourceHook isn't defined if you pass the shell the --fuzzing-safe +// option. Skip this test silently, to avoid spurious failures. +if (typeof withSourceHook != 'function') + quit(0); + +let g = newGlobal(); +let dbg = new Debugger(g); + +function test(source) { + // To ensure that we're getting the value the source hook returns, make + // it differ from the actual source. + let frobbed = source.replace(/debugger/, 'reggubed'); + let log = ''; + + withSourceHook(function (url) { + log += 's'; + assertEq(url, "BanalBivalve.jsm"); + return frobbed; + }, () => { + dbg.onDebuggerStatement = function (frame) { + log += 'd'; + assertEq(frame.script.source.text, frobbed); + } + + g.evaluate(source, { fileName: "BanalBivalve.jsm", + sourceIsLazy: true }); + }); + + assertEq(log, 'ds'); +} + +test("debugger; // Ignominious Iguana"); +test("(function () { debugger; /* Meretricious Marmoset */})();"); +test("(() => { debugger; })(); // Gaunt Gibbon"); diff --git a/js/src/jit-test/tests/debug/Source-url.js b/js/src/jit-test/tests/debug/Source-url.js new file mode 100644 index 000000000..02f822b3a --- /dev/null +++ b/js/src/jit-test/tests/debug/Source-url.js @@ -0,0 +1,10 @@ +// Source.prototype.url can be a string or null. + +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +for (var fileName of ['file:///var/foo.js', null]) { + g.evaluate("function f(x) { return 2*x; }", {fileName: fileName}); + var fw = gw.getOwnPropertyDescriptor('f').value; + assertEq(fw.script.source.url, fileName); +} diff --git a/js/src/jit-test/tests/debug/breakpoint-01.js b/js/src/jit-test/tests/debug/breakpoint-01.js new file mode 100644 index 000000000..6da5a3ff4 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-01.js @@ -0,0 +1,22 @@ +// Basic breakpoint test. + +var g = newGlobal(); +g.s = ''; +var handler = { + hit: function (frame) { + assertEq(this, handler); + g.s += '1'; + } +}; +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + g.s += '0'; + var line0 = frame.script.getOffsetLocation(frame.offset).lineNumber; + var offs = frame.script.getLineOffsets(line0 + 2); + for (var i = 0; i < offs.length; i++) + frame.script.setBreakpoint(offs[i], handler); +}; +g.eval("debugger;\n" + + "s += 'a';\n" + // line0 + 1 + "s += 'b';\n"); // line0 + 2 +assertEq(g.s, "0a1b"); diff --git a/js/src/jit-test/tests/debug/breakpoint-02.js b/js/src/jit-test/tests/debug/breakpoint-02.js new file mode 100644 index 000000000..3f02a6c38 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-02.js @@ -0,0 +1,15 @@ +// Setting a breakpoint in a non-debuggee Script is an error. + +load(libdir + "asserts.js"); + +var g1 = newGlobal(); +var g2 = g1.eval("newGlobal('same-compartment')"); +g2.eval("function f() { return 2; }"); +g1.f = g2.f; + +var dbg = Debugger(g1); +var s; +dbg.onDebuggerStatement = function (frame) { s = frame.eval("f").return.script; }; +g1.eval("debugger;"); + +assertThrowsInstanceOf(function () { s.setBreakpoint(0, {}); }, Error); diff --git a/js/src/jit-test/tests/debug/breakpoint-03.js b/js/src/jit-test/tests/debug/breakpoint-03.js new file mode 100644 index 000000000..c5fd986bc --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-03.js @@ -0,0 +1,16 @@ +// Setting a breakpoint in a script we are no longer debugging is an error. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = Debugger(); +var gobj = dbg.addDebuggee(g); +g.eval("function f() { return 2; }"); + +var s; +dbg.onDebuggerStatement = function (frame) { s = frame.eval("f").return.script; }; +g.eval("debugger;"); +s.setBreakpoint(0, {}); // ok + +dbg.removeDebuggee(gobj); +assertThrowsInstanceOf(function () { s.setBreakpoint(0, {}); }, Error); diff --git a/js/src/jit-test/tests/debug/breakpoint-04.js b/js/src/jit-test/tests/debug/breakpoint-04.js new file mode 100644 index 000000000..67d5e7e40 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-04.js @@ -0,0 +1,30 @@ +// Hitting a breakpoint with no hit method does nothing. + +var g = newGlobal(); +g.s = ''; +g.eval("var line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " debugger;\n" + // line0 + 2 + " s += 'x';\n" + // line0 + 3 + "}\n") +var dbg = Debugger(g); +var bp = []; +dbg.onDebuggerStatement = function (frame) { + g.s += 'D'; + var arr = frame.script.getLineOffsets(g.line0 + 3); + for (var i = 0; i < arr.length; i++) { + var obj = {}; + bp[i] = obj; + frame.script.setBreakpoint(arr[i], obj); + } +}; + +g.f(); +assertEq(g.s, "Dx"); + +dbg.onDebuggerStatement = undefined; + +for (var i = 0; i < bp.length; i++) + bp[i].hit = function () { g.s += 'B'; }; +g.f(); +assertEq(g.s, "DxBx"); diff --git a/js/src/jit-test/tests/debug/breakpoint-05.js b/js/src/jit-test/tests/debug/breakpoint-05.js new file mode 100644 index 000000000..217bb823a --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-05.js @@ -0,0 +1,19 @@ +// If the offset parameter to setBreakpoint is invalid, throw an error. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = new Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame) { + // We assume at least one offset between 0 and frame.offset is invalid. + assertThrowsInstanceOf( + function () { + for (var i = 0; i < frame.offset; i++) + frame.script.setBreakpoint(i, {}); + }, + Error); + hits++; +}; +g.eval("x = 256; debugger;"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/breakpoint-06.js b/js/src/jit-test/tests/debug/breakpoint-06.js new file mode 100644 index 000000000..9a7f78670 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-06.js @@ -0,0 +1,20 @@ +// The argument to a breakpoint hit method is a frame. + +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +dbg.onDebuggerStatement = function (frame1) { + function hit(frame2) { + assertEq(frame2, frame1); + hits++; + } + var s = frame1.script; + var offs = s.getLineOffsets(g.line0 + 2); + for (var i = 0; i < offs.length; i++) + s.setBreakpoint(offs[i], {hit: hit}); +}; +g.eval("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "x = 1;\n"); // line0 + 2 +assertEq(hits, 1); +assertEq(g.x, 1); diff --git a/js/src/jit-test/tests/debug/breakpoint-07.js b/js/src/jit-test/tests/debug/breakpoint-07.js new file mode 100644 index 000000000..d7a168ba8 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-07.js @@ -0,0 +1,30 @@ +// Code runs fine if do-nothing breakpoints are set on every line. + +var g = newGlobal(); +var src = ("var line0 = Error().lineNumber;\n" + + "function gcd(a, b) {\n" + // line0 + 1 + " if (a > b)\n" + // line0 + 2 + " return gcd(b, a);\n" + // line0 + 3 + " var c = b % a;\n" + // line0 + 4 + " if (c === 0)\n" + // line0 + 5 + " return a;\n" + // line0 + 6 + " return gcd(c, a);\n" + // line0 + 7 + "}\n"); // line0 + 8 +g.eval(src); + +var dbg = Debugger(g); +var hits = 0 ; +dbg.onDebuggerStatement = function (frame) { + var s = frame.eval("gcd").return.script; + var offs; + for (var lineno = g.line0 + 2; (offs = s.getLineOffsets(lineno)).length > 0; lineno++) { + for (var i = 0; i < offs.length; i++) + s.setBreakpoint(offs[i], {hit: function (f) { hits++; }}); + } + assertEq(lineno > g.line0 + 7, true); + assertEq(lineno <= g.line0 + 9, true); +}; + +g.eval("debugger;"); +assertEq(g.gcd(31 * 7 * 5 * 3 * 2, 11 * 3 * 3 * 2), 6); +assertEq(hits >= 18, true); diff --git a/js/src/jit-test/tests/debug/breakpoint-08.js b/js/src/jit-test/tests/debug/breakpoint-08.js new file mode 100644 index 000000000..b1011cb49 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-08.js @@ -0,0 +1,31 @@ +// Breakpoints are dropped from eval scripts when they finish executing. +// (The eval cache doesn't cache breakpoints.) + +var g = newGlobal(); + +g.line0 = undefined; +g.eval("function f() {\n" + + " return eval(s);\n" + + "}\n"); +g.s = ("line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "result = 'ok';\n"); // line0 + 2 + +var dbg = Debugger(g); +var hits = 0, bphits = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.type, 'eval'); + assertEq(frame.script.getBreakpoints().length, 0); + var h = {hit: function (frame) { bphits++; }}; + var offs = frame.script.getLineOffsets(g.line0 + 2); + for (var i = 0; i < offs.length; i++) + frame.script.setBreakpoint(offs[i], h); + hits++; +}; + +for (var i = 0; i < 3; i++) { + assertEq(g.f(), 'ok'); + assertEq(g.result, 'ok'); +} +assertEq(hits, 3); +assertEq(bphits, 3); diff --git a/js/src/jit-test/tests/debug/breakpoint-09.js b/js/src/jit-test/tests/debug/breakpoint-09.js new file mode 100644 index 000000000..cf3b81f4f --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-09.js @@ -0,0 +1,13 @@ +// Setting a breakpoint in an eval script that is not on the stack. Bug 746973. +// We don't assert that the breakpoint actually hits because that depends on +// the eval cache, an implementation detail. + +var g = newGlobal(); +var dbg = Debugger(g); +g.eval("function f() { return eval('2+2'); }"); +var s; +dbg.onNewScript = function (script) { s = script; }; +g.f(); +for (var offset of s.getLineOffsets(s.startLine)) + s.setBreakpoint(offset, {hit: function () {}}); +assertEq(g.f(), 4); diff --git a/js/src/jit-test/tests/debug/breakpoint-10.js b/js/src/jit-test/tests/debug/breakpoint-10.js new file mode 100644 index 000000000..cf42e9063 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-10.js @@ -0,0 +1,19 @@ +var g = newGlobal(); +var dbg = new Debugger(g); + +var fscript = null; +dbg.onNewScript = function(script) { + dbg.onNewScript = undefined; + fscript = script.getChildScripts()[0]; +} + +g.eval("function f(x) { arguments[0] = 3; return x }"); +assertEq(fscript !== null, true); + +fscript.setBreakpoint(0, {hit:function(frame) { + assertEq(frame.eval('x').return, 1); + assertEq(frame.arguments[0], 1); + return {return:42}; +}}); + +assertEq(g.f(1), 42); diff --git a/js/src/jit-test/tests/debug/breakpoint-11.js b/js/src/jit-test/tests/debug/breakpoint-11.js new file mode 100644 index 000000000..f29544095 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-11.js @@ -0,0 +1,40 @@ +// Setting a breakpoint in a generator function works, and we can +// traverse the stack and evaluate expressions in the context of older +// generator frames. + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + function hit(frame) { + assertEq(frame.generator, true); + assertEq(frame.older.generator, true); + frame.older.eval("q += 16"); + } + + var s = frame.script; + var offs = s.getLineOffsets(g.line0 + 9); + for (var i = 0; i < offs.length; i++) + s.setBreakpoint(offs[i], {hit: hit}); +}; + +g.eval("line0 = Error().lineNumber;\n" + + "function* g(x) {\n" + // + 1 + " var q = 10;\n" + // + 2 + " yield* x;\n" + // + 3 + " return q;\n" + // + 4 + "}\n" + // + 5 + "function* range(n) {\n" + // + 6 + " debugger;\n" + // + 7 + " for (var i = 0; i < n; i++)\n" + // + 8 + " yield i;\n" + // + 9 <-- breakpoint + " return;\n" + // so that line 9 only has the yield + "}"); + +g.eval("var iter = g(range(2))"); +g.eval("var first = iter.next().value"); +g.eval("var second = iter.next().value"); +g.eval("var third = iter.next().value"); + +assertEq(g.first, 0); +assertEq(g.second, 1); +assertEq(g.third, 42); diff --git a/js/src/jit-test/tests/debug/breakpoint-12.js b/js/src/jit-test/tests/debug/breakpoint-12.js new file mode 100644 index 000000000..54f48b7c6 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-12.js @@ -0,0 +1,78 @@ +// Removing a global as a debuggee forgets all its breakpoints. + + +var dbgA = new Debugger; +var logA = ''; + +var dbgB = new Debugger; +var logB = ''; + +var g1 = newGlobal(); +g1.eval('function g1f() { print("Weltuntergang"); }'); +var DOAg1 = dbgA.addDebuggee(g1); +var DOAg1f = DOAg1.getOwnPropertyDescriptor('g1f').value; +DOAg1f.script.setBreakpoint(0, { hit: () => { logA += '1'; } }); + +var DOBg1 = dbgB.addDebuggee(g1); +var DOBg1f = DOBg1.getOwnPropertyDescriptor('g1f').value; +DOBg1f.script.setBreakpoint(0, { hit: () => { logB += '1'; } }); + + +var g2 = newGlobal(); +g2.eval('function g2f() { print("Mokushi"); }'); + +var DOAg2 = dbgA.addDebuggee(g2); +var DOAg2f = DOAg2.getOwnPropertyDescriptor('g2f').value; +DOAg2f.script.setBreakpoint(0, { hit: () => { logA += '2'; } }); + +var DOBg2 = dbgB.addDebuggee(g2); +var DOBg2f = DOBg2.getOwnPropertyDescriptor('g2f').value; +DOBg2f.script.setBreakpoint(0, { hit: () => { logB += '2'; } }); + +assertEq(logA, ''); +assertEq(logB, ''); +g1.g1f(); +g2.g2f(); +assertEq(logA, '12'); +assertEq(logB, '12'); +logA = logB = ''; + +// Removing a global as a debuggee should make its breakpoint not hit. +dbgA.removeDebuggee(g2); +dbgB.removeDebuggee(g1); +assertEq(logA, ''); +assertEq(logB, ''); +g1.g1f(); +g2.g2f(); +assertEq(logA, '1'); +assertEq(logB, '2'); +logA = logB = ''; + +// Adding the global back as a debuggee should not resurrect its breakpoints. +dbgA.addDebuggee(g2); +dbgB.addDebuggee(g1); +assertEq(logA, ''); +assertEq(logB, ''); +g1.g1f(); +g2.g2f(); +assertEq(logA, '1'); +assertEq(logB, '2'); +logA = logB = ''; + +// But, we can set them again, and it all works. +DOAg2f.script.setBreakpoint(0, { hit: () => { logA += '2'; } }); +assertEq(logA, ''); +assertEq(logB, ''); +g2.g2f(); +g1.g1f(); +assertEq(logA, '21'); +assertEq(logB, '2'); +logA = logB = ''; + +DOBg1f.script.setBreakpoint(0, { hit: () => { logB += '1'; } }); +assertEq(logA, ''); +assertEq(logB, ''); +g2.g2f(); +g1.g1f(); +assertEq(logA, '21'); +assertEq(logB, '21'); diff --git a/js/src/jit-test/tests/debug/breakpoint-13.js b/js/src/jit-test/tests/debug/breakpoint-13.js new file mode 100644 index 000000000..f054fb535 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-13.js @@ -0,0 +1,13 @@ +// Breakpoints should be hit on scripts gotten not via Debugger.Frame. + +var g = newGlobal(); +g.eval("function f(x) { return x + 1; }"); +// Warm up so f gets OSRed into the jits. +g.eval("for (var i = 0; i < 10000; i++) f(i);"); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var fw = gw.getOwnPropertyDescriptor("f").value; +var hits = 0; +fw.script.setBreakpoint(0, { hit: function(frame) { hits++; } }); +g.eval("f(42);"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/breakpoint-14.js b/js/src/jit-test/tests/debug/breakpoint-14.js new file mode 100644 index 000000000..98196ee9b --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-14.js @@ -0,0 +1,14 @@ +// Breakpoints should be hit on scripts gotten not via Debugger.Frame. + +var g = newGlobal(); +g.eval("function f(x) { return x + 1; }"); +g.eval("function g(x) { f(x); }"); +// Warm up so f gets OSRed into the jits and g inlines f. +g.eval("for (var i = 0; i < 10000; i++) g(i);"); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +var fw = gw.getOwnPropertyDescriptor("f").value; +var hits = 0; +fw.script.setBreakpoint(0, { hit: function(frame) { hits++; } }); +g.eval("g(42);"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/breakpoint-gc-01.js b/js/src/jit-test/tests/debug/breakpoint-gc-01.js new file mode 100644 index 000000000..db454ced1 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-gc-01.js @@ -0,0 +1,25 @@ +// Handlers for breakpoints in an eval script are live as long as the script is on the stack. + +var g = newGlobal(); +var dbg = Debugger(g); +var log = ''; +dbg.onDebuggerStatement = function (frame) { + function handler(i) { + return {hit: function () { log += '' + i; }}; + } + + var s = frame.script; + var offs = s.getLineOffsets(g.line0 + 2); + for (var i = 0; i < 7; i++) { + var h = handler(i); + for (var j = 0; j < offs.length; j++) + s.setBreakpoint(offs[j], h); + } + gc(); +}; + + +g.eval("var line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "x = 1;\n"); // line0 + 2 +assertEq(log, '0123456'); diff --git a/js/src/jit-test/tests/debug/breakpoint-gc-02.js b/js/src/jit-test/tests/debug/breakpoint-gc-02.js new file mode 100644 index 000000000..09b1f846c --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-gc-02.js @@ -0,0 +1,28 @@ +// A Debugger with live breakpoints is live. + +var g = newGlobal(); +g.eval("var line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " return 2;\n" + // line0 + 2 + "}\n"); + +var N = 4; +var hits = 0; +for (var i = 0; i < N; i++) { + var dbg = Debugger(g); + dbg.onDebuggerStatement = function (frame) { + var handler = {hit: function () { hits++; }}; + var s = frame.eval("f").return.script; + var offs = s.getLineOffsets(g.line0 + 2); + for (var j = 0; j < offs.length; j++) + s.setBreakpoint(offs[j], handler); + }; + g.eval('debugger;'); + dbg.onDebuggerStatement = undefined; + dbg = null; +} + +gc(); + +assertEq(g.f(), 2); +assertEq(hits, N); diff --git a/js/src/jit-test/tests/debug/breakpoint-gc-04.js b/js/src/jit-test/tests/debug/breakpoint-gc-04.js new file mode 100644 index 000000000..121bc656a --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-gc-04.js @@ -0,0 +1,23 @@ +// Enabled debuggers keep breakpoint handlers alive. + +var g = newGlobal(); +g.eval("var line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " return 2;\n" + // line0 + 2 + "}\n"); +var N = 4; +var hits = 0; +for (var i = 0; i < N; i++) { + var dbg = Debugger(g); + dbg.onDebuggerStatement = function (frame) { + var handler = {hit: function () { hits++; }}; + var s = frame.eval("f").return.script; + var offs = s.getLineOffsets(g.line0 + 2); + for (var j = 0; j < offs.length; j++) + s.setBreakpoint(offs[j], handler); + }; +} +g.eval('debugger;'); +gc({}); +assertEq(g.f(), 2); +assertEq(hits, N); diff --git a/js/src/jit-test/tests/debug/breakpoint-gc-05.js b/js/src/jit-test/tests/debug/breakpoint-gc-05.js new file mode 100644 index 000000000..0639f0269 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-gc-05.js @@ -0,0 +1,25 @@ +// Disabled debuggers keep breakpoint handlers alive. + +var g = newGlobal(); +g.eval("var line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " return 2;\n" + // line0 + 2 + "}\n"); +var N = 4; +var hits = 0; +for (var i = 0; i < N; i++) { + var dbg = Debugger(g); + dbg.onDebuggerStatement = function (frame) { + var handler = {hit: function () { hits++; }}; + var s = frame.eval("f").return.script; + var offs = s.getLineOffsets(g.line0 + 2); + for (var j = 0; j < offs.length; j++) + s.setBreakpoint(offs[j], handler); + }; +} +g.eval('debugger;'); +dbg.enabled = false; +gc({}); +dbg.enabled = true; +assertEq(g.f(), 2); +assertEq(hits, N); diff --git a/js/src/jit-test/tests/debug/breakpoint-multi-01.js b/js/src/jit-test/tests/debug/breakpoint-multi-01.js new file mode 100644 index 000000000..d0afdd5c7 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-multi-01.js @@ -0,0 +1,28 @@ +// A single Debugger object can set multiple breakpoints at an instruction. + +var g = newGlobal(); +var dbg = Debugger(g); +var log = ''; +dbg.onDebuggerStatement = function (frame) { + log += 'D'; + function handler(i) { + return {hit: function (frame) { log += '' + i; }}; + } + var f = frame.eval("f").return; + var s = f.script; + var offs = s.getLineOffsets(g.line0 + 2); + for (var i = 0; i < 10; i++) { + var bp = handler(i); + for (var j = 0; j < offs.length; j++) + s.setBreakpoint(offs[j], bp); + } + assertEq(f.call().return, 42); + log += 'X'; +}; + +g.eval("var line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " return 42;\n" + // line0 + 2 + "}\n" + + "debugger;\n"); +assertEq(log, 'D0123456789X'); diff --git a/js/src/jit-test/tests/debug/breakpoint-multi-02.js b/js/src/jit-test/tests/debug/breakpoint-multi-02.js new file mode 100644 index 000000000..d75afce47 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-multi-02.js @@ -0,0 +1,42 @@ +// After clearing one breakpoint, another breakpoint at the same instruction still works. + +var g = newGlobal(); +var dbg = Debugger(g); +var script = null; +var handlers = []; +dbg.onDebuggerStatement = function (frame) { + function handler(i) { + return {hit: function (frame) { g.log += '' + i; }}; + } + var f = frame.eval("f").return; + var s = f.script; + if (script === null) + script = s; + else + assertEq(s, script); + + var offs = s.getLineOffsets(g.line0 + 3); + for (var i = 0; i < 3; i++) { + handlers[i] = handler(i); + for (var j = 0; j < offs.length; j++) + s.setBreakpoint(offs[j], handlers[i]); + } +}; + +g.eval("var line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " log += 'x';\n" + // line0 + 2 + " log += 'y';\n" + // line0 + 3 + "}\n" + + "debugger;\n"); +assertEq(handlers.length, 3); + +g.log = ''; +g.f(); +assertEq(g.log, 'x012y'); + +script.clearBreakpoint(handlers[0]); + +g.log = ''; +g.f(); +assertEq(g.log, 'x12y'); diff --git a/js/src/jit-test/tests/debug/breakpoint-multi-03.js b/js/src/jit-test/tests/debug/breakpoint-multi-03.js new file mode 100644 index 000000000..3a87be938 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-multi-03.js @@ -0,0 +1,27 @@ +// Multiple Debugger objects can set breakpoints at the same instruction. + +var g = newGlobal(); +function attach(g, i) { + var dbg = Debugger(g); + dbg.onDebuggerStatement = function (frame) { + var s = frame.eval("f").return.script; + var offs = s.getLineOffsets(g.line0 + 3); + for (var j = 0; j < offs.length; j++) + s.setBreakpoint(offs[j], {hit: function () { g.log += "" + i; }}); + }; +} + +g.eval("var line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " log += 'a';\n" + // line0 + 2 + " log += 'b';\n" + // line0 + 3 + "}\n"); + +for (var i = 0; i < 3; i++) + attach(g, i); + +g.log = ''; +g.eval('debugger;'); +g.log += 'x'; +g.f(); +assertEq(g.log, 'xa012b'); diff --git a/js/src/jit-test/tests/debug/breakpoint-multi-04.js b/js/src/jit-test/tests/debug/breakpoint-multi-04.js new file mode 100644 index 000000000..2dba62615 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-multi-04.js @@ -0,0 +1,48 @@ +// After clearing one breakpoint, another breakpoint from a different Debugger object at the same instruction still works. + +function test(which) { + var g = newGlobal(); + g.eval("var line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " return " + which + ";\n" + // line0 + 2 + "}\n"); + + var log; + var scripts = []; + var handlers = []; + function addDebugger(g, i) { + var dbg = Debugger(g); + dbg.onDebuggerStatement = function (frame) { + var s = frame.eval("f").return.script; + scripts[i] = s; + var offs = s.getLineOffsets(g.line0 + 2); + var handler = {hit: function (frame) { log += '' + i; } }; + s.setBreakpoint(0, handler); + handlers[i] = handler; + }; + } + + var expected = ''; + for (var i = 0; i < 3; i++) { + addDebugger(g, i); + if (i !== which) + expected += '' + i; + } + g.eval('debugger;'); + + for (var i = 0; i < 3; i++) + assertEq(scripts[i].getBreakpoints()[0], handlers[i]); + + log = ''; + g.f(); + assertEq(log, '012'); + + scripts[which].clearAllBreakpoints(); + + log = ''; + g.f(); + assertEq(log, expected); +} + +for (var j = 0; j < 3; j++) + test(j); diff --git a/js/src/jit-test/tests/debug/breakpoint-noncng.js b/js/src/jit-test/tests/debug/breakpoint-noncng.js new file mode 100644 index 000000000..7a45fa2af --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-noncng.js @@ -0,0 +1,20 @@ +// Breakpoints work in non-compile-and-go code. Bug 738479. + +var g = newGlobal(); +g.s = ''; +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); +g.evaluate( + "function f() {\n" + // fscript.startLine + " s += 'a';\n" + // fscript.startLine + 1 + " s += 'b';\n" + // fscript.startLine + 2 + "}\n"); + +var fscript = gw.makeDebuggeeValue(g.f).script; +var handler = {hit: function (frame) { g.s += '1'; }}; +for (var pc of fscript.getLineOffsets(fscript.startLine + 2)) + fscript.setBreakpoint(pc, handler); + +g.f(); + +assertEq(g.s, "a1b"); diff --git a/js/src/jit-test/tests/debug/breakpoint-resume-01.js b/js/src/jit-test/tests/debug/breakpoint-resume-01.js new file mode 100644 index 000000000..932f4d5f9 --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-resume-01.js @@ -0,0 +1,25 @@ +// A breakpoint handler hit method can return {throw: exc} to throw an exception. + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + function hit(frame) { + return {throw: frame.eval("new Error('PASS')").return}; + } + + var s = frame.script; + var offs = s.getLineOffsets(g.line0 + 3); + for (var i = 0; i < offs.length; i++) + s.setBreakpoint(offs[i], {hit: hit}); +}; + +g.s = null; +g.eval("line0 = Error().lineNumber;\n" + + "debugger;\n" + // line0 + 1 + "try {\n" + // line0 + 2 + " s = 'FAIL';\n" + // line0 + 3 + "} catch (exc) {\n" + + " assertEq(exc.constructor, Error);\n" + + " s = exc.message;\n" + + "}\n"); +assertEq(g.s, 'PASS'); diff --git a/js/src/jit-test/tests/debug/breakpoint-resume-02.js b/js/src/jit-test/tests/debug/breakpoint-resume-02.js new file mode 100644 index 000000000..397e2ca3a --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-resume-02.js @@ -0,0 +1,34 @@ +// A breakpoint handler hit method can return null to raise an uncatchable error. + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + g.log += 'D'; + + function hit(frame) { + g.log += 'H'; + return null; + } + + var f = frame.eval("f").return; + var s = f.script; + var offs = s.getLineOffsets(g.line0 + 3); + for (var i = 0; i < offs.length; i++) + s.setBreakpoint(offs[i], {hit: hit}); + + var rv = f.call(); + assertEq(rv, null); + g.log += 'X'; +}; + +g.log = ''; +g.eval("line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " try {\n" + // line0 + 2 + " log += '3';\n" + // line0 + 3 + " } catch (exc) {\n" + + " log += '5';\n" + + " }\n" + + "}\n" + + "debugger;\n"); +assertEq(g.log, 'DHX'); diff --git a/js/src/jit-test/tests/debug/breakpoint-resume-03.js b/js/src/jit-test/tests/debug/breakpoint-resume-03.js new file mode 100644 index 000000000..e57ecc6fe --- /dev/null +++ b/js/src/jit-test/tests/debug/breakpoint-resume-03.js @@ -0,0 +1,30 @@ +// A breakpoint handler hit method can return {return: val} to force the frame to return. + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (frame) { + g.log += 'D'; + + function hit(frame) { + g.log += 'H'; + return {return: 'ok'}; + } + + var f = frame.eval("f").return; + var s = f.script; + var offs = s.getLineOffsets(g.line0 + 2); + for (var i = 0; i < offs.length; i++) + s.setBreakpoint(offs[i], {hit: hit}); + + var rv = f.call(); + assertEq(rv.return, 'ok'); + g.log += 'X'; +}; + +g.log = ''; +g.eval("line0 = Error().lineNumber;\n" + + "function f() {\n" + // line0 + 1 + " log += '2';\n" + // line0 + 2 + "}\n" + + "debugger;\n"); +assertEq(g.log, 'DHX'); diff --git a/js/src/jit-test/tests/debug/bug-1102549.js b/js/src/jit-test/tests/debug/bug-1102549.js new file mode 100644 index 000000000..d7bb893f2 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-1102549.js @@ -0,0 +1,9 @@ +// |jit-test| error: log is not defined + +if (!this.Promise) + throw new Error('log is not defined'); + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onPromiseSettled = function (g) { log += 's'; throw "foopy"; }; +g.settlePromiseNow(new g.Promise(function (){})); diff --git a/js/src/jit-test/tests/debug/bug-1103386.js b/js/src/jit-test/tests/debug/bug-1103386.js new file mode 100644 index 000000000..4c1777a35 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-1103386.js @@ -0,0 +1,10 @@ +load(libdir + "immutable-prototype.js"); + +// Random chosen test: js/src/jit-test/tests/auto-regress/bug700295.js +if (globalPrototypeChainIsMutable()) { + __proto__ = null; + Object.prototype.__proto__ = this; +} + +// Random chosen test: js/src/jit-test/tests/debug/Memory-takeCensus-05.js +Debugger(newGlobal()).memory.takeCensus(); diff --git a/js/src/jit-test/tests/debug/bug-1103813.js b/js/src/jit-test/tests/debug/bug-1103813.js new file mode 100644 index 000000000..07d7fe1f5 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-1103813.js @@ -0,0 +1,6 @@ +// Random chosen test: js/src/jit-test/tests/debug/Source-invisible.js +newGlobal({ + invisibleToDebugger: true +}) +// Random chosen test: js/src/jit-test/tests/debug/Debugger-findObjects-05.js +x = (new Debugger).findObjects() diff --git a/js/src/jit-test/tests/debug/bug-1103817.js b/js/src/jit-test/tests/debug/bug-1103817.js new file mode 100644 index 000000000..dc79ff20c --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-1103817.js @@ -0,0 +1,5 @@ +// Random chosen test: js/src/jit-test/tests/debug/Source-introductionScript-04.js +x = (new Debugger).addDebuggee(newGlobal()); +print(x.getOwnPropertyDescriptor('Function').value.proto.script); +// Random chosen test: js/src/jit-test/tests/debug/Memory-takeCensus-03.js +(new Debugger).memory.takeCensus(); diff --git a/js/src/jit-test/tests/debug/bug-1110327.js b/js/src/jit-test/tests/debug/bug-1110327.js new file mode 100644 index 000000000..0e2a66fc0 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-1110327.js @@ -0,0 +1,5 @@ +// Randomly chosen test: js/src/jit-test/tests/debug/Debugger-debuggees-10.js +x = newGlobal() +x.t = this +// Randomly chosen test: js/src/jit-test/tests/debug/Debugger-findObjects-06.js +Debugger(x).findObjects() diff --git a/js/src/jit-test/tests/debug/bug-1136806.js b/js/src/jit-test/tests/debug/bug-1136806.js new file mode 100644 index 000000000..104e79b1e --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-1136806.js @@ -0,0 +1,7 @@ +// |jit-test| allow-unhandlable-oom; allow-oom + +if (typeof oomAfterAllocations == "function") { + Debugger() + oomAfterAllocations(6) + Debugger() +} diff --git a/js/src/jit-test/tests/debug/bug-1192401.js b/js/src/jit-test/tests/debug/bug-1192401.js new file mode 100644 index 000000000..82bc233d7 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-1192401.js @@ -0,0 +1,5 @@ +const dbg = new Debugger(); +const g = evalcx("lazy"); +dbg.addDebuggee(g); +dbg.memory.trackingAllocationSites = true; +g.eval("this.alloc = {}"); diff --git a/js/src/jit-test/tests/debug/bug-1238610.js b/js/src/jit-test/tests/debug/bug-1238610.js new file mode 100644 index 000000000..369d7550d --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-1238610.js @@ -0,0 +1,27 @@ +// |jit-test| allow-oom + +if (!('oomAfterAllocations' in this)) + quit(); + +if (helperThreadCount() === 0) + quit(0); + +lfcode = new Array(); +dbg = Debugger(); +dbg.onEnterFrame = function() {}; +g = newGlobal(); +lfcode.push(` + oomAfterAllocations(100); + new Number(); + dbg.addDebuggee(g); +`) +file = lfcode.shift(); +loadFile(file); +function loadFile(lfVarx) { + lfGlobal = newGlobal() + for (lfLocal in this) + if (!(lfLocal in lfGlobal)) + lfGlobal[lfLocal] = this[lfLocal] + offThreadCompileScript(lfVarx) + lfGlobal.runOffThreadScript() +} diff --git a/js/src/jit-test/tests/debug/bug-1240090.js b/js/src/jit-test/tests/debug/bug-1240090.js new file mode 100644 index 000000000..8749a03f3 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-1240090.js @@ -0,0 +1,24 @@ +gczeal(2); +g = newGlobal(); +dbg = Debugger(g); +dbg.onNewScript = function() function() this; +schedulegc(10); +g.eval("setLazyParsingDisabled(true)"); +g.evaluate("function one() {}"); +g.evaluate(` + function target () {} + function two2() {} + `, {}); +g.evaluate(` + function three1() {} + function three2() {} + function three3() {} + `, {}); +dbg.memory.takeCensus({ + breakdown: { + by: "coarseType", + scripts: { + by: "filename" + } + } +}); diff --git a/js/src/jit-test/tests/debug/bug-1248162.js b/js/src/jit-test/tests/debug/bug-1248162.js new file mode 100644 index 000000000..1beee180d --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-1248162.js @@ -0,0 +1,14 @@ +// |jit-test| allow-oom + +if (typeof oomTest !== "function") + quit(); + +// Adapted from randomly chosen test: js/src/jit-test/tests/debug/Debugger-onNewGlobalObject-01.js +for (var i = 0; i < 9; ++i) { + var dbg = new Debugger; + dbg.onNewGlobalObject = function() {}; +} +// jsfunfuzz-generated +oomTest(function() { + newGlobal(); +}) diff --git a/js/src/jit-test/tests/debug/bug-1260725.js b/js/src/jit-test/tests/debug/bug-1260725.js new file mode 100644 index 000000000..7719c70b5 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-1260725.js @@ -0,0 +1,12 @@ +// |jit-test| error: out of memory + +if (!('oomTest' in this)) + throw new Error("out of memory"); + +var dbg = new Debugger; +dbg.onNewGlobalObject = function(global) { + dbg.memory.takeCensus({}); +}; +oomTest(function() { + newGlobal({}) +}); diff --git a/js/src/jit-test/tests/debug/bug-1260728.js b/js/src/jit-test/tests/debug/bug-1260728.js new file mode 100644 index 000000000..fbccb448e --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-1260728.js @@ -0,0 +1,12 @@ +// |jit-test| error:Error + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onNewScript = function(script) { + fscript = script.getChildScripts()[0]; +} +g.eval("function f(x) { arguments[0] = 3; return x }"); +fscript.setBreakpoint(0, {hit:function(frame) { + assertEq(frame.eval("assertEq(arguments, undefined)").return, 1); +}}); +assertEq(g.f(1), 42); diff --git a/js/src/jit-test/tests/debug/bug-725733.js b/js/src/jit-test/tests/debug/bug-725733.js new file mode 100644 index 000000000..bdd2f21e3 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-725733.js @@ -0,0 +1,9 @@ +// |jit-test| +// Adding a debuggee must leave its scripts in a safe state. + +var g = newGlobal(); +g.eval( + "function f(x) { return {q: x}; }\n" + + "var n = f('').q;\n"); +var dbg = new Debugger(g); +g.eval("f(0)"); diff --git a/js/src/jit-test/tests/debug/bug-800586.js b/js/src/jit-test/tests/debug/bug-800586.js new file mode 100644 index 000000000..a8a983c59 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-800586.js @@ -0,0 +1,7 @@ +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); +dbg.onDebuggerStatement = function (f) { + gw.executeInGlobal("eval('var x = \"A Brief History of Love\"');\n") +}; +g.eval('debugger'); diff --git a/js/src/jit-test/tests/debug/bug-826669.js b/js/src/jit-test/tests/debug/bug-826669.js new file mode 100644 index 000000000..f26ccba08 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-826669.js @@ -0,0 +1,7 @@ +gczeal(9, 2) +var g1 = newGlobal(); +var g2 = newGlobal(); +var dbg = new Debugger(); +var g1w = dbg.addDebuggee(g1); +g1.eval('function f() {}'); +scripts = dbg.findScripts({}); diff --git a/js/src/jit-test/tests/debug/bug-858170.js b/js/src/jit-test/tests/debug/bug-858170.js new file mode 100644 index 000000000..a45002a59 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-858170.js @@ -0,0 +1,7 @@ +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onNewScript = function (s) { + throw new Error(); +}; +dbg.uncaughtExceptionHook = function () {} +g.eval("2 * 3"); diff --git a/js/src/jit-test/tests/debug/bug-876654.js b/js/src/jit-test/tests/debug/bug-876654.js new file mode 100644 index 000000000..e66939f81 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug-876654.js @@ -0,0 +1,13 @@ +// |jit-test| +// Exercise finding a DebuggerSource cross compartment wrapper in +// JSCompartment::findOutgoingEdges() + +let g = newGlobal(); +let dbg = new Debugger(g); +dbg.onNewScript = function (script) { + var text = script.source.text; +} +g.eval("function f() { function g() {} }"); +gczeal(9, 1) +var a = {} +var b = {} diff --git a/js/src/jit-test/tests/debug/bug1001372.js b/js/src/jit-test/tests/debug/bug1001372.js new file mode 100644 index 000000000..1f0bc78fa --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1001372.js @@ -0,0 +1,21 @@ +var lfcode = new Array(); +lfcode.push = loadFile; +lfcode.push(""); +lfcode.push(""); +lfcode.push("3"); +lfcode.push(""); +lfcode.push(""); +lfcode.push(""); +lfcode.push(""); +lfcode.push("4"); +lfcode.push(""); +lfcode.push("0"); +lfcode.push("gczeal(2);"); +lfcode.push("\ + var g = newGlobal();\ + g.parent = this;\ + g.eval('new Debugger(parent).onExceptionUnwind = function() {};');\ + "); +function loadFile(lfVarx) { + evaluate(lfVarx); +} diff --git a/js/src/jit-test/tests/debug/bug1002797.js b/js/src/jit-test/tests/debug/bug1002797.js new file mode 100644 index 000000000..f7cbcfccf --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1002797.js @@ -0,0 +1,15 @@ +var lfcode = new Array(); +lfcode.push(""); +lfcode.push("0"); +lfcode.push("newGlobal(\"1\").eval(\"(new Debugger).addAllGlobalsAsDebuggees();\");\n"); +while (true) { + var file = lfcode.shift(); if (file == undefined) { break; } + loadFile(file) +} +function loadFile(lfVarx) { + try { + if (lfVarx.substr(-3) != ".js" && lfVarx.length != 1) { + evaluate(lfVarx); + } else if (!isNaN(lfVarx)) {} + } catch (lfVare) { } +} diff --git a/js/src/jit-test/tests/debug/bug1004447.js b/js/src/jit-test/tests/debug/bug1004447.js new file mode 100644 index 000000000..e9a289fa0 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1004447.js @@ -0,0 +1,23 @@ +// Tests that earlier try notes don't interfere with later exception handling. + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.eval("(" + function () { + dbg = new Debugger(debuggeeGlobal); +} + ")();"); +var myObj = { p1: 'a', } +try { + with(myObj) { + do { + throw value; + } while(false); + } +} catch(e) { + // The above is expected to throw. +} + +try { + if(!(p1 === 1)) { } +} catch (e) { + // The above is expected to throw. +} diff --git a/js/src/jit-test/tests/debug/bug1006205.js b/js/src/jit-test/tests/debug/bug1006205.js new file mode 100644 index 000000000..bf4a1de3e --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1006205.js @@ -0,0 +1,20 @@ +var lfcode = new Array(); +lfcode.push = loadFile; +lfcode.push("\ +var g = newGlobal();\ +g.debuggeeGlobal = this;\ +g.eval(\"(\" + function () {\ + dbg = new Debugger(debuggeeGlobal);\ + } + \")();\");\ +"); +lfcode.push("gc();"); +lfcode.push("\ +var g = newGlobal();\ +g.debuggeeGlobal = this;\ +g.eval(\"(\" + function () {\ + dbg = new Debugger(debuggeeGlobal);\ +} + \")();\");\ +"); +function loadFile(lfVarx) { +function newFunc(x) { new Function(x)(); }; newFunc(lfVarx); +} diff --git a/js/src/jit-test/tests/debug/bug1006473.js b/js/src/jit-test/tests/debug/bug1006473.js new file mode 100644 index 000000000..48fd9129b --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1006473.js @@ -0,0 +1,19 @@ +// |jit-test| error: ReferenceError + +var lfcode = new Array(); +lfcode.push("gczeal(4);"); +lfcode.push("setJitCompilerOption('ion.warmup.trigger', 30);"); +lfcode.push("\ +var g = newGlobal();\ +g.parent = this;\ +g.eval('function f(frame, exc) { f2 = function () { return exc; }; exc = 123; }');\ +g.eval('new Debugger(parent).onExceptionUnwind = f;');\ +var obj = int8 ('oops');\ +"); +while (true) { + var file = lfcode.shift(); if (file == undefined) { break; } + loadFile(file) +} +function loadFile(lfVarx) { + evaluate(lfVarx); +} diff --git a/js/src/jit-test/tests/debug/bug1106164.js b/js/src/jit-test/tests/debug/bug1106164.js new file mode 100644 index 000000000..40759439e --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1106164.js @@ -0,0 +1,17 @@ +// |jit-test| error: ReferenceError +var g = newGlobal(); +g.parent = this; +g.eval("new Debugger(parent).onExceptionUnwind = function () { };"); +evaluate("\ + var tokenCodes = {};\ + tokenCodes.continue = 0;\ + var arr = [\ + (0.E87 ), \ + ];\ + for(var reportCompare in tokenCodes) {\ + for(var p1 in arr) {\ + if(arr[j . arr ++ ] === p) {\ + }\ + }\ + }\ +", { noScriptRval : true, isRunOnce: true }); diff --git a/js/src/jit-test/tests/debug/bug1106719.js b/js/src/jit-test/tests/debug/bug1106719.js new file mode 100644 index 000000000..87eb3da94 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1106719.js @@ -0,0 +1,11 @@ +// |jit-test| allow-oom; allow-overrecursed + +g = newGlobal() +g.parent = this +g.eval("Debugger(parent).onExceptionUnwind=(function(){})") +gcparam("maxBytes", gcparam("gcBytes")) +function f() { + f() + y(arguments) +} +f() diff --git a/js/src/jit-test/tests/debug/bug1107525.js b/js/src/jit-test/tests/debug/bug1107525.js new file mode 100644 index 000000000..e80ba0f64 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1107525.js @@ -0,0 +1,9 @@ +// |jit-test| error: InternalError +enableSPSProfiling(); +var g = newGlobal(); +g.parent = this; +g.eval("new Debugger(parent).onExceptionUnwind = function () { hits++; };"); +function f() { + var x = f(); +} +f(); diff --git a/js/src/jit-test/tests/debug/bug1107913.js b/js/src/jit-test/tests/debug/bug1107913.js new file mode 100644 index 000000000..b1ba03ba2 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1107913.js @@ -0,0 +1,7 @@ +// |jit-test| error: TypeError + +var g = newGlobal(); +g.parent = this; +g.eval("new Debugger(parent).onExceptionUnwind = function () {};"); +Object.preventExtensions(this); +evaluate("function testcase() { }", { noScriptRval : true, isRunOnce: true }); diff --git a/js/src/jit-test/tests/debug/bug1108159.js b/js/src/jit-test/tests/debug/bug1108159.js new file mode 100644 index 000000000..d1a84c7be --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1108159.js @@ -0,0 +1,15 @@ +// |jit-test| slow + +if (helperThreadCount() == 0) + quit(0); + +var s = ''; +for (var i = 0; i < 70000; i++) + { + s += 'function x' + i + '() { x' + i + '(); }\n'; +} +evaluate(s); +var g = newGlobal(); +(new Debugger).addDebuggee(g); +g.offThreadCompileScript('debugger;',{}); +g.runOffThreadScript(); diff --git a/js/src/jit-test/tests/debug/bug1108556.js b/js/src/jit-test/tests/debug/bug1108556.js new file mode 100644 index 000000000..2e399bc6d --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1108556.js @@ -0,0 +1,10 @@ +// |jit-test| error: ReferenceError + +var g = newGlobal(); +g.parent = this; +g.eval("new Debugger(parent).onExceptionUnwind = function () { hits++; };"); +evaluate('\ +var fe="v";\ +for (i=0; String.fromCharCode(0x004E); i++)\ + fe += fe;\ +', { isRunOnce: true }); diff --git a/js/src/jit-test/tests/debug/bug1109328.js b/js/src/jit-test/tests/debug/bug1109328.js new file mode 100644 index 000000000..f369bde66 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1109328.js @@ -0,0 +1,7 @@ +try { + gcslice(0)(""()); +} catch (e) {} +g = newGlobal() +g.parent = this +g.eval("Debugger(parent).onExceptionUnwind=(function(){})"); +gcparam("maxBytes", gcparam("gcBytes")); diff --git a/js/src/jit-test/tests/debug/bug1109915.js b/js/src/jit-test/tests/debug/bug1109915.js new file mode 100644 index 000000000..7a11215fa --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1109915.js @@ -0,0 +1,17 @@ +var evalInFrame = (function (global) { + var dbgGlobal = newGlobal(); + var dbg = new dbgGlobal.Debugger(); + return function evalInFrame(upCount, code) { + dbg.addDebuggee(global); + var frame = dbg.getNewestFrame().older; + var completion = frame.eval(code); + }; +})(this); +function g1(x, args) {} +function f1(x, y, o) { + for (var i=0; i<50; i++) { + o.apply(evalInFrame(0, "x"), x); + } +} +var o1 = {apply: g1}; +assertEq(f1(3, 5, o1), undefined); diff --git a/js/src/jit-test/tests/debug/bug1109964.js b/js/src/jit-test/tests/debug/bug1109964.js new file mode 100644 index 000000000..42e708991 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1109964.js @@ -0,0 +1,10 @@ +var dbgGlobal = newGlobal(); +var dbg = new dbgGlobal.Debugger(); +dbg.addDebuggee(this); +function f() { + var a = arguments; + a[1]; + dbg.getNewestFrame().eval("a"); +} +f(); + diff --git a/js/src/jit-test/tests/debug/bug1111199.js b/js/src/jit-test/tests/debug/bug1111199.js new file mode 100644 index 000000000..30bce2e56 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1111199.js @@ -0,0 +1,17 @@ +g = newGlobal() +g.parent = this +g.eval("Debugger(parent).onExceptionUnwind=(function(){})") +try { +function f(code) { + n = parseInt('', 0); + return g("try{}catch(e){}", n) +} +function g(s, n) { + s2 = s + s + d = (n - (function () { + return "" + this.id + eval.id; + } )().abstract) / 2 + m = g(s2, d) +} +f("switch(''){default:break;}") +} catch(exc1) {} diff --git a/js/src/jit-test/tests/debug/bug1114587.js b/js/src/jit-test/tests/debug/bug1114587.js new file mode 100644 index 000000000..538419b03 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1114587.js @@ -0,0 +1,26 @@ +// |jit-test| error: TypeError +var lfcode = new Array(); +lfcode.push(""); +lfcode.push(""); +lfcode.push("\ +var g = newGlobal();\ +g.debuggeeGlobal = this;\ +g.eval('(' + function () {\ + dbg = new Debugger(debuggeeGlobal);\ + dbg.onExceptionUnwind = function (frame, exc) {\ + var s = '!';\ + for (var f = frame; f; f = f.older)\ + s += f.callee.name;\ + };\ + } + ')();');\ +Debugger(17) = this;\ +"); +while (true) { + var file = lfcode.shift(); if (file == undefined) { break; } + loadFile(file) +} +function loadFile(lfVarx) { + if (lfVarx.substr(-3) != ".js" && lfVarx.length != 1) { + function newFunc(x) { new Function(x)(); }; newFunc(lfVarx); + } +} diff --git a/js/src/jit-test/tests/debug/bug1116103.js b/js/src/jit-test/tests/debug/bug1116103.js new file mode 100644 index 000000000..2c3fd0698 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1116103.js @@ -0,0 +1,11 @@ +// |jit-test| error: ReferenceError + +evaluate(` + var g = newGlobal(); + g.parent = this; + g.eval('new Debugger(parent).onExceptionUnwind = function() {};'); +`) +{ + while (x && 0) {} + let x +} diff --git a/js/src/jit-test/tests/debug/bug1118878.js b/js/src/jit-test/tests/debug/bug1118878.js new file mode 100644 index 000000000..91e6e8cb9 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1118878.js @@ -0,0 +1,11 @@ + +var evalInFrame = (function (global) { + var dbgGlobal = newGlobal(); + var dbg = new dbgGlobal.Debugger(); + return function evalInFrame(upCount, code) { + dbg.addDebuggee(global); + var frame = dbg.getNewestFrame().older; + var completion = frame.eval(code); + }; +})(this); +evaluate("for (var k in 'xxx') (function g() { Math.atan2(42); evalInFrame((0), (''), true); })();", { noScriptRval : true, isRunOnce : true }); diff --git a/js/src/jit-test/tests/debug/bug1121083.js b/js/src/jit-test/tests/debug/bug1121083.js new file mode 100644 index 000000000..ef4ff69c8 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1121083.js @@ -0,0 +1,16 @@ +// |jit-test| error:terminated +options('werror'); + +g = newGlobal(); +g.parent = this; +g.eval("Debugger(parent).onExceptionUnwind = function () {};"); + +function f(x) { + if (x === 0) { + return; + } + f(x - 1); + f(x - 1); +} +timeout(0.00001); +f(100); diff --git a/js/src/jit-test/tests/debug/bug1130756.js b/js/src/jit-test/tests/debug/bug1130756.js new file mode 100644 index 000000000..24664f7fc --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1130756.js @@ -0,0 +1,28 @@ +// |jit-test| error: timeout + +options('werror'); + +var g = newGlobal(); +g.parent = this; +g.eval("(" + function() { +var dbg = Debugger(parent); +var handler = {hit: function() {}}; + +dbg.onEnterFrame = function(frame) { + frame.onStep = function() {} +} +} + ")()"); + +g = newGlobal(); +g.parent = this; +g.eval("Debugger(parent).onExceptionUnwind = function () {};"); + +function f(x) { + if (x === 0) { + return; + } + f(x - 1); + f(x - 1); +} +timeout(0.00001); +f(100); diff --git a/js/src/jit-test/tests/debug/bug1130768.js b/js/src/jit-test/tests/debug/bug1130768.js new file mode 100644 index 000000000..4c16a3132 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1130768.js @@ -0,0 +1,12 @@ +// |jit-test| error:foo +var g = newGlobal(); +g.parent = this; +g.eval("(" + function() { + var dbg = new Debugger(parent); + count = 0; + dbg.onExceptionUnwind = function(frame) { + frame.onPop = function() { if (count++ < 30) frame.eval("x = 3"); }; + }; +} + ")()"); +Object.defineProperty(this, "x", {set: [].map}); +throw "foo"; diff --git a/js/src/jit-test/tests/debug/bug1133196.js b/js/src/jit-test/tests/debug/bug1133196.js new file mode 100644 index 000000000..60d963a8b --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1133196.js @@ -0,0 +1,16 @@ +// |jit-test| error: TypeError + +var g = newGlobal(); +g.parent = this; +g.eval("(" + function() { + var dbg = new Debugger(parent); + dbg.onExceptionUnwind = function(frame) { + frame.older.onStep = function() {} + }; +} + ")()"); +function f() { + (function inner(arr) { + inner(arr.map); + })([]); +} +f(); diff --git a/js/src/jit-test/tests/debug/bug1147939.js b/js/src/jit-test/tests/debug/bug1147939.js new file mode 100644 index 000000000..a2bd2cf79 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1147939.js @@ -0,0 +1,8 @@ +// |jit-test| error: TypeError +var g = newGlobal(); +g.debuggeeGlobal = this; +g.eval("(" + function () { + dbg = new Debugger(debuggeeGlobal); + dbg.onExceptionUnwind = Map; +} + ")();"); +throw new Error("oops"); diff --git a/js/src/jit-test/tests/debug/bug1148917.js b/js/src/jit-test/tests/debug/bug1148917.js new file mode 100644 index 000000000..2645f913f --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1148917.js @@ -0,0 +1,14 @@ +// |jit-test| error: Error + +var g = newGlobal(); +g.eval('function f(a) { evaluate("f(" + " - 1);", {newContext: true}); }'); +var dbg = new Debugger(g); +var frames = []; +dbg.onEnterFrame = function (frame) { + if (frames.length == 3) + return; + frames.push(frame); + for (var f of frames) + f.eval('a').return +}; +g.f(); diff --git a/js/src/jit-test/tests/debug/bug1160182.js b/js/src/jit-test/tests/debug/bug1160182.js new file mode 100644 index 000000000..a1b732894 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1160182.js @@ -0,0 +1,11 @@ +var g = newGlobal(); +g.eval("(" + function() { + var o = {get x() {}}; + this.method = Object.getOwnPropertyDescriptor(o, "x").get; + assertEq(isLazyFunction(method), true); +} + ")()"); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); +var scripts = dbg.findScripts(); +var methodv = gw.makeDebuggeeValue(g.method); +assertEq(scripts.indexOf(methodv.script) != -1, true); diff --git a/js/src/jit-test/tests/debug/bug1161332.js b/js/src/jit-test/tests/debug/bug1161332.js new file mode 100644 index 000000000..2b8f37149 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1161332.js @@ -0,0 +1,16 @@ +// |jit-test| error: Error + +var g = newGlobal(); +g.eval('function f(a) { if (a == 1) debugger; evaluate("f(" + a + " - 1);"); }'); +var N = 2; +var dbg = new Debugger(g); +var frames = []; +dbg.onEnterFrame = function (frame) { + frames.push(frame); + frame.onPop = function () { assertEq(frame.onPop, frame.onPop); }; +}; +dbg.onDebuggerStatement = function (frame) { + for (var f of frames) + f.eval('a').return; +}; +evaluate("g.f(N);"); diff --git a/js/src/jit-test/tests/debug/bug1188334.js b/js/src/jit-test/tests/debug/bug1188334.js new file mode 100644 index 000000000..a5c0a77c4 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1188334.js @@ -0,0 +1,18 @@ +var evalInFrame = (function (global) { + var dbgGlobal = newGlobal(); + var dbg = new dbgGlobal.Debugger(); + return function evalInFrame(upCount, code) { + dbg.addDebuggee(global); + var frame = dbg.getNewestFrame().older; + var completion = frame.eval(code); + }; +})(this); +function f() { + { + let {} = "xxx"; + yield evalInFrame(0, "x"); + } +} +var gen = f(); +gen.next() + diff --git a/js/src/jit-test/tests/debug/bug1191499.js b/js/src/jit-test/tests/debug/bug1191499.js new file mode 100644 index 000000000..07086c4cb --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1191499.js @@ -0,0 +1,17 @@ + +setJitCompilerOption('ion.warmup.trigger', 2); +setJitCompilerOption('offthread-compilation.enable', 0); +var g = newGlobal(); +var dbg2 = new Debugger; +g.toggle = function toggle(x, d) { + if (d) { + dbg2.addDebuggee(g); + dbg2.getNewestFrame().environment.getVariable("x"); + } +}; +g.eval("" + function f(x, d) { toggle(++arguments, d); }); +g.eval("(" + function test() { + for (var i = 0; i < 30; i++) + f(42, false); + f(42, true); +} + ")();"); diff --git a/js/src/jit-test/tests/debug/bug1216261.js b/js/src/jit-test/tests/debug/bug1216261.js new file mode 100644 index 000000000..71f11fe80 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1216261.js @@ -0,0 +1,15 @@ +// |jit-test| exitstatus: 3 + +if (!('oomAfterAllocations' in this)) + quit(3); + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onDebuggerStatement = function(frame) { + oomAfterAllocations(5); + // OOMs here, and possibly again in the error reporter when trying to + // report the OOM, so the shell just exits with code 3. + frame.older.eval("escaped = function() { return y }"); +} +g.eval("function h() { debugger }"); +g.eval("(function () { var y = {p:42}; h(); yield })().next();"); diff --git a/js/src/jit-test/tests/debug/bug1219905.js b/js/src/jit-test/tests/debug/bug1219905.js new file mode 100644 index 000000000..d7bc83527 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1219905.js @@ -0,0 +1,14 @@ +// |jit-test| allow-oom + +// We need allow-oom here because the debugger reports an uncaught exception if +// it hits OOM calling the exception unwind hook. This causes the shell to exit +// with the OOM reason. + +if (!('oomTest' in this)) + quit(); + +var g = newGlobal(); +g.parent = this; +g.eval("new Debugger(parent).onExceptionUnwind = function() {}"); +let finished = false; +oomTest(() => l, false); diff --git a/js/src/jit-test/tests/debug/bug1221378.js b/js/src/jit-test/tests/debug/bug1221378.js new file mode 100644 index 000000000..d8e6781df --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1221378.js @@ -0,0 +1,11 @@ +// Bug 1221378: allocating an array from within the object metadata callback +// shouldn't cause re-entrant resolution of lazy prototypes. + +// To test for this regression, we need some way to make the code that collects +// metadata about object allocations allocate an Array. Presently, +// enableShellObjectMetadataCallback installs a callback that does this, but if +// that hook gets removed (in production there's only ever one callback we +// actually use), then the ability to make whatever metadata collection code +// remains allocate an Array will cover this regression. For example, it could +// be a flag that one can only set in debug builds from TestingFunctions.cpp. +newGlobal().eval('enableShellAllocationMetadataBuilder(); Array'); diff --git a/js/src/jit-test/tests/debug/bug1232655.js b/js/src/jit-test/tests/debug/bug1232655.js new file mode 100644 index 000000000..b55acdc85 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1232655.js @@ -0,0 +1,5 @@ +g = newGlobal(); +g.log = ""; +Debugger(g).onDebuggerStatement = frame => frame.eval("log += this.Math.toString();"); +g.eval("(function() { with ({}) debugger })()"); +assertEq(g.log, "[object Math]"); diff --git a/js/src/jit-test/tests/debug/bug1240546.js b/js/src/jit-test/tests/debug/bug1240546.js new file mode 100644 index 000000000..58efe086e --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1240546.js @@ -0,0 +1,12 @@ +// |jit-test| allow-oom + +if (!('oomAfterAllocations' in this)) + quit(); + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.eval("(" + function() { + oomAfterAllocations(100); + var dbg = Debugger(debuggeeGlobal); + dbg.onEnterFrame = function(frame) {} +} + ")()"); diff --git a/js/src/jit-test/tests/debug/bug1240803.js b/js/src/jit-test/tests/debug/bug1240803.js new file mode 100644 index 000000000..84f6d17e5 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1240803.js @@ -0,0 +1,24 @@ +// |jit-test| allow-oom + + +if (!('oomAfterAllocations' in this)) + quit(); + +(function() { + g = newGlobal() + dbg = new Debugger + g.toggle = function(d) { + if (d) { + dbg.addDebuggee(g); + dbg.getNewestFrame(); + oomAfterAllocations(2); + setBreakpoint; + } + } + g.eval("" + function f(d) toggle(d)) + g.eval("(" + function() { + f(false); + f(true); + } + ")()") +})(); + diff --git a/js/src/jit-test/tests/debug/bug1242111.js b/js/src/jit-test/tests/debug/bug1242111.js new file mode 100644 index 000000000..4b365ad5f --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1242111.js @@ -0,0 +1,11 @@ +// |jit-test| allow-oom + +if (!('oomAfterAllocations' in this)) + quit(); + +var g = newGlobal(); +g.debuggeeGlobal = []; +g.eval("(" + function() { + oomAfterAllocations(57); + Debugger(debuggeeGlobal).onEnterFrame = function() {} +} + ")()"); diff --git a/js/src/jit-test/tests/debug/bug1242798.js b/js/src/jit-test/tests/debug/bug1242798.js new file mode 100644 index 000000000..8e4b74b22 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1242798.js @@ -0,0 +1,14 @@ + +var g = newGlobal(); +var dbg = new Debugger(g); +g.eval("" + function f(c) { + if (c == 0) + return; + if (c == 2) + debugger; + f(c-1); + for (var i = 0; i < 100; i++) + Debugger += newGlobal('#15: myObj.parseFloat !== parseFloat'); +}); +dbg.onDebuggerStatement = function (frame) {}; +g.eval("f(2)"); diff --git a/js/src/jit-test/tests/debug/bug1245862.js b/js/src/jit-test/tests/debug/bug1245862.js new file mode 100644 index 000000000..8cfeafbbe --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1245862.js @@ -0,0 +1,25 @@ +// |jit-test| allow-oom + +if (!('oomAfterAllocations' in this)) + quit(); + +var g = newGlobal(); +var dbg = new Debugger; +g.h = function h(d) { + if (d) { + dbg.addDebuggee(g); + var f = dbg.getNewestFrame().older; + f.st_p1((oomAfterAllocations(10)) + "foo = 'string of 42'"); + } +} +g.eval("" + function f(d) { + g(d); +}); +g.eval("" + function g(d) { + h(d); +}); +g.eval("(" + function () { + for (i = 0; i < 5; i++) + f(false); + assertEq(f(true), "string of 42"); +} + ")();"); diff --git a/js/src/jit-test/tests/debug/bug1246605.js b/js/src/jit-test/tests/debug/bug1246605.js new file mode 100644 index 000000000..cac1fa186 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1246605.js @@ -0,0 +1,13 @@ +g = newGlobal(); +dbg = Debugger(g); +dbg.onNewScript = function(script) { fscript = script.getChildScripts()[0]; } +g.eval("function f() { arguments[0]; }"); +var hitBreakpoint = false; +fscript.setBreakpoint(0, { + hit: function() { + getBacktrace({ args: 1 }); + hitBreakpoint = true; + } +}); +g.f(""); +assertEq(hitBreakpoint, true); diff --git a/js/src/jit-test/tests/debug/bug1251919.js b/js/src/jit-test/tests/debug/bug1251919.js new file mode 100644 index 000000000..188bfa393 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1251919.js @@ -0,0 +1,13 @@ +// |jit-test| error: out of memory + +if (!('oomTest' in this)) + throw new Error("out of memory"); + +// jsfunfuzz-generated +fullcompartmentchecks(true); +// Adapted from randomly chosen test: js/src/jit-test/tests/debug/bug-1248162.js +var dbg = new Debugger; +dbg.onNewGlobalObject = function() {}; +oomTest(function() { + newGlobal(); +}) diff --git a/js/src/jit-test/tests/debug/bug1252453.js b/js/src/jit-test/tests/debug/bug1252453.js new file mode 100644 index 000000000..79f56528f --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1252453.js @@ -0,0 +1,21 @@ +// |jit-test| --no-threads + +lfcode = new Array(); +gczeal(8,2); +lfcode.push(` +const root = newGlobal(); +const dbg = new Debugger; +const wrappedRoot = dbg.addDebuggee(root); +dbg.memory.trackingAllocationSites = 1; +root.eval("(" + function immediate() { '_' << foo } + "())"); +`); +file = lfcode.shift(); +loadFile(file); +function loadFile(lfVarx) { + try { + function newFunc(x) Function(x)(); + newFunc(lfVarx)(); + } catch (lfVare) { + print(lfVare) + } +} diff --git a/js/src/jit-test/tests/debug/bug1252464.js b/js/src/jit-test/tests/debug/bug1252464.js new file mode 100644 index 000000000..48c6f8ec0 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1252464.js @@ -0,0 +1,15 @@ +// |jit-test| error: ReferenceError + +g = newGlobal(); +dbg = Debugger(g); +hits = 0; +dbg.onNewScript = function () hits++; +assertEq(g.eval("eval('2 + 3')"), 5); +this.gczeal(hits,1); +dbg = Debugger(g); +g.h = function () { + var env = dbg.getNewestFrame().environment; + dbg = 0; + assertThrowsInstanceOf; +} +g.eval("h()"); diff --git a/js/src/jit-test/tests/debug/bug1253246.js b/js/src/jit-test/tests/debug/bug1253246.js new file mode 100644 index 000000000..440959a8c --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1253246.js @@ -0,0 +1,5 @@ +var g = newGlobal(); +var dbg = new Debugger(g); + +dbg.onDebuggerStatement = (frame) => { frame.eval("c = 42;"); }; +g.evalReturningScope("'use strict'; debugger;"); diff --git a/js/src/jit-test/tests/debug/bug1254123.js b/js/src/jit-test/tests/debug/bug1254123.js new file mode 100644 index 000000000..51b2c8940 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1254123.js @@ -0,0 +1,17 @@ +// |jit-test| error: boom + +if (!('oomTest' in this)) + throw new Error("boom"); + +evaluate(` +function ERROR(msg) { + throw new Error("boom"); +} +for (var i = 0; i < 9; ++ i) { + var dbg = new Debugger; + dbg.onNewGlobalObject = ERROR; +} +oomTest(function() { + newGlobal(); +}) +`); diff --git a/js/src/jit-test/tests/debug/bug1254190.js b/js/src/jit-test/tests/debug/bug1254190.js new file mode 100644 index 000000000..0d95ce76f --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1254190.js @@ -0,0 +1,15 @@ +// |jit-test| error: out of memory; slow; + +if (!('oomTest' in this)) + throw new Error("out of memory"); + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onNewScript = function (s) { + log += dbg.findScripts({ source: s.source }).length; +} +log = ""; +oomTest(() => { + var static = newGlobal(); + g.eval("(function() {})()"); +}); diff --git a/js/src/jit-test/tests/debug/bug1254578.js b/js/src/jit-test/tests/debug/bug1254578.js new file mode 100644 index 000000000..828faf186 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1254578.js @@ -0,0 +1,23 @@ +// |jit-test| error:ReferenceError; slow + +if (!('oomTest' in this)) + throw (new ReferenceError); + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.eval("(" + function() { + dbg = new Debugger(debuggeeGlobal); + dbg.onExceptionUnwind = function(frame, exc) { + var s = '!'; + for (var f = frame; f; f = f.older) + debuggeeGlobal.log += s; + }; +} + ")();"); +var dbg = new Debugger; +dbg.onNewGlobalObject = function(global) { + get.seen = true; +}; +oomTest(function() { + newGlobal({ + }) +}); diff --git a/js/src/jit-test/tests/debug/bug1257045.js b/js/src/jit-test/tests/debug/bug1257045.js new file mode 100644 index 000000000..a6437b308 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1257045.js @@ -0,0 +1,11 @@ +if (!wasmIsSupported()) + quit(); + +fullcompartmentchecks(true); +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onNewScript = (function(script) { + s = script; +}) +g.eval(`new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" 0))')));`); +s.source; diff --git a/js/src/jit-test/tests/debug/bug1263899.js b/js/src/jit-test/tests/debug/bug1263899.js new file mode 100644 index 000000000..89f121e83 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1263899.js @@ -0,0 +1,29 @@ +try { + evaluate(` + function runTestCase() $ERROR() + function $ERROR() { + throw Error + } + Object.defineProperty(this, "x", { value: 0 }); + setJitCompilerOption("ion.warmup.trigger", 0) + `) + evaluate(`function f() {} f(x)`) + runTestCase() +} catch (exc) {} +evaluate(` + g = newGlobal() + g.parent = this + g.eval("(" + function() { + Debugger(parent).onExceptionUnwind = function(frame) { + frame.older + } + } + ")()") + try { $ERROR() } catch(e){} +`) +try { +evaluate(` + x ^= null; + if (x = 1) + $ERROR() +`); +} catch(e) {} diff --git a/js/src/jit-test/tests/debug/bug1264961.js b/js/src/jit-test/tests/debug/bug1264961.js new file mode 100644 index 000000000..c0ae7aaa4 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1264961.js @@ -0,0 +1,28 @@ +// |jit-test| slow; + +if (!('oomTest' in this)) + quit(); + +loadFile(` + var o = {} + var global = this; + var p = new Proxy(o, { + "deleteProperty": function (await , key) { + var g = newGlobal(); + g.parent = global; + g.eval("var dbg = new Debugger(parent); dbg.onEnterFrame = function(frame) {};"); + } + }) + for (var i=0; i<100; i++); + assertEq(delete p.foo, true); +`); +function loadFile(lfVarx) { + var k = 0; + oomTest(function() { + // In practice a crash occurs before iteration 4000. + if (k++ > 4000) + quit(); + eval(lfVarx); + }) +} + diff --git a/js/src/jit-test/tests/debug/bug1266434.js b/js/src/jit-test/tests/debug/bug1266434.js new file mode 100644 index 000000000..7e4a7a08d --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1266434.js @@ -0,0 +1,8 @@ +var g = newGlobal(); +var dbg = new Debugger(g); +var g = newGlobal(); +g.evaluate("function f(x) { return x + 1; }"); +var gw = dbg.addDebuggee(g); +gczeal(2, 1); +var s = dbg.findScripts(); +gczeal(0); diff --git a/js/src/jit-test/tests/debug/bug1272908.js b/js/src/jit-test/tests/debug/bug1272908.js new file mode 100644 index 000000000..f4c573684 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1272908.js @@ -0,0 +1,19 @@ +// |jit-test| error: out of memory; slow; + +// Adapted from randomly chosen test: js/src/jit-test/tests/modules/bug-1233915.js +g = newGlobal(); +g.parent = this; +g.eval("(" + function() { + Debugger(parent).onExceptionUnwind = function(frame) + frame.eval("") +} + ")()"); +// Adapted from randomly chosen test: js/src/jit-test/tests/debug/bug1254123.js +function ERROR(msg) { + throw new Error("boom"); +} +var dbg = new Debugger; +dbg.onNewGlobalObject = ERROR; +oomTest(function() { + newGlobal(); +}) + diff --git a/js/src/jit-test/tests/debug/bug1275001.js b/js/src/jit-test/tests/debug/bug1275001.js new file mode 100644 index 000000000..b8bdc5556 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1275001.js @@ -0,0 +1,30 @@ + +g = newGlobal(); +g.parent = this; +g.eval("(" + function() { + Debugger(parent).onExceptionUnwind = function(frame) { + frame.older + } +} + ")()") +function check_one(expected, f, err) { + try { + f() + } catch (ex) { + s = ex.toString() + assertEq(s.slice(11, -err.length), expected) + } +} +ieval = eval +function check(expr, expected = expr) { + var end, err + for ([end, err] of[[".random_prop", " is undefined" ]]) + statement = "o = {};" + expr + end; + cases = [ + function() ieval("var undef;" + statement), + Function(statement) + ] + for (f of cases) + check_one(expected, f, err) +} +check("undef"); +check("o.b"); diff --git a/js/src/jit-test/tests/debug/bug1282741.js b/js/src/jit-test/tests/debug/bug1282741.js new file mode 100644 index 000000000..9f342f9cf --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1282741.js @@ -0,0 +1,28 @@ + +function removeAdd(dbg, g) { + dbg.removeDebuggee(g); + dbg.addDebuggee(g); + switch (dbg.removeDebuggee(g)) {} +} +function newGlobalDebuggerPair(toggleSeq) { + var g = newGlobal(); + var dbg = new Debugger; + dbg.addDebuggee(g); + g.eval("" + function f() { + for (var i = 0; i < 100; i++) interruptIf(i == 95); + }); + setInterruptCallback(function() { + return true; + }); + return [g, dbg]; +} +function testEpilogue(toggleSeq) { + var [g, dbg] = newGlobalDebuggerPair(toggleSeq); + dbg.onEnterFrame = function(f) { + f.onPop = function() { + toggleSeq(dbg, g); + } + }; + g.f() +} +testEpilogue(removeAdd); diff --git a/js/src/jit-test/tests/debug/bug1299121.js b/js/src/jit-test/tests/debug/bug1299121.js new file mode 100644 index 000000000..4139c9772 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1299121.js @@ -0,0 +1,10 @@ +var g = newGlobal(); +g.parent = this; +g.eval("(" + function() { + var dbg = new Debugger(parent); + dbg.onExceptionUnwind = function(frame) { + frame.eval("h = 3"); + }; +} + ")()"); +g = function h() { } +g(); diff --git a/js/src/jit-test/tests/debug/bug1300517.js b/js/src/jit-test/tests/debug/bug1300517.js new file mode 100644 index 000000000..4170d5feb --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1300517.js @@ -0,0 +1,12 @@ +// |jit-test| error: ReferenceError +g = newGlobal(); +g.log *= ""; +Debugger(g).onDebuggerStatement = frame => frame.eval("log += this.Math.toString();"); +let forceException = g.eval(` + (class extends class {} { + constructor() { + debugger; + } + }) +`); +new forceException; diff --git a/js/src/jit-test/tests/debug/bug1300528.js b/js/src/jit-test/tests/debug/bug1300528.js new file mode 100644 index 000000000..9078fdd74 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1300528.js @@ -0,0 +1,34 @@ +load(libdir + "asserts.js"); + +if (helperThreadCount() === 0) + quit(0); + +function BigInteger(a, b, c) {} +function montConvert(x) { + var r = new BigInteger(null); + return r; +} +var ba = new Array(); +a = new BigInteger(ba); +g = montConvert(a); +var lfGlobal = newGlobal(); +for (lfLocal in this) { + if (!(lfLocal in lfGlobal)) { + lfGlobal[lfLocal] = this[lfLocal]; + } +} +lfGlobal.offThreadCompileScript(` + var dbg = new Debugger(g); + dbg.onEnterFrame = function (frame) { + var frameThis = frame.this; + } +`); +lfGlobal.runOffThreadScript(); +assertThrowsInstanceOf(test, ReferenceError); +function test() { + function check(fun, msg, todo) { + success = fun(); + } + check(() => Object.getPrototypeOf(view) == Object.getPrototypeOf(simple)); + typeof this; +} diff --git a/js/src/jit-test/tests/debug/bug1302432.js b/js/src/jit-test/tests/debug/bug1302432.js new file mode 100644 index 000000000..6d99326eb --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1302432.js @@ -0,0 +1,10 @@ +setJitCompilerOption('ion.warmup.trigger', 0); +gczeal(7, 1); +var dbgGlobal = newGlobal(); +var dbg = new dbgGlobal.Debugger(); +dbg.addDebuggee(this); +function f(x, await = () => Array.isArray(revocable.proxy), ...get) { + dbg.getNewestFrame().older.eval("print(a)"); +} +function a() {} +for (var i = 0; i < 10; i++) f(); diff --git a/js/src/jit-test/tests/debug/bug1308578.js b/js/src/jit-test/tests/debug/bug1308578.js new file mode 100644 index 000000000..c44b546a9 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug1308578.js @@ -0,0 +1,10 @@ +// |jit-test| error: ReferenceError + +g = newGlobal(); +g.parent = this; +g.eval("new Debugger(parent).onExceptionUnwind = function () {}"); +a = new class extends Array { + constructor() { + for (;; ([] = p)) {} + } +} diff --git a/js/src/jit-test/tests/debug/bug911065.js b/js/src/jit-test/tests/debug/bug911065.js new file mode 100644 index 000000000..ad539ae3d --- /dev/null +++ b/js/src/jit-test/tests/debug/bug911065.js @@ -0,0 +1,34 @@ +var g = newGlobal(); +var dbg = new Debugger; +var gw = dbg.addDebuggee(g); + +g.eval(` // 1 +var line0 = Error().lineNumber; // 2 +function f() { // 3 + for (var x of [0]) { // 4 + if (true == false) // 5 + return false; // 6, aka line0 + 4 + } // 7 + return true; // 8 +} // 9 +`); // 10 + +if (g.dis) + g.dis(g.f); + +var script = gw.getOwnPropertyDescriptor("f").value.script; + +print("Debugger's view:"); +print("----------------"); +for (var i = script.startLine; i <= script.startLine + script.lineCount; i++) { + print("Line " + i + ": " + JSON.stringify(script.getLineOffsets(i))); +} + +var hits = 0; +var handler = {hit: function () { hits++; }}; +var offs = script.getLineOffsets(g.line0 + 4); +for (var i = 0; i < offs.length; i++) + script.setBreakpoint(offs[i], handler); + +assertEq(g.f(), true); +assertEq(hits, 0); diff --git a/js/src/jit-test/tests/debug/bug967039.js b/js/src/jit-test/tests/debug/bug967039.js new file mode 100644 index 000000000..93350368e --- /dev/null +++ b/js/src/jit-test/tests/debug/bug967039.js @@ -0,0 +1,6 @@ +var g1 = newGlobal(); +var dbg = Debugger(g1); +g1.dbg = dbg; +g1.eval("function foo() { dbg.removeDebuggee(this); }"); +g1.eval("function f() { try { throw 3; } catch(e) { foo(); } }\n"); +g1.f(); diff --git a/js/src/jit-test/tests/debug/bug973566.js b/js/src/jit-test/tests/debug/bug973566.js new file mode 100644 index 000000000..5f332f164 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug973566.js @@ -0,0 +1,7 @@ +Object.prototype[1] = 'peek'; +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onEnterFrame = function (frame) { + var lines = frame.script.getAllOffsets(); +}; +g.eval("1;"); diff --git a/js/src/jit-test/tests/debug/bug980585.js b/js/src/jit-test/tests/debug/bug980585.js new file mode 100644 index 000000000..48988733c --- /dev/null +++ b/js/src/jit-test/tests/debug/bug980585.js @@ -0,0 +1,10 @@ +var g = newGlobal(); +var dbg = new Debugger(g); + +try { + g.eval("function f() { [1].map(function () {}); const x = 42; x = 43; } f();"); +} catch (e) { + // Ignore the syntax error. +} + +dbg.findScripts(); diff --git a/js/src/jit-test/tests/debug/bug999655.js b/js/src/jit-test/tests/debug/bug999655.js new file mode 100644 index 000000000..0b9ce4d07 --- /dev/null +++ b/js/src/jit-test/tests/debug/bug999655.js @@ -0,0 +1,11 @@ + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onNewScript = function(script) { + fscript = script.getChildScripts()[0]; +} +g.eval("function f(x) { arguments[0] = 3; return x }"); +fscript.setBreakpoint(0, {hit:function(frame) { + assertEq(frame.arguments[0], 1); +}}); +g.f(1); diff --git a/js/src/jit-test/tests/debug/class-01.js b/js/src/jit-test/tests/debug/class-01.js new file mode 100644 index 000000000..66f390bd4 --- /dev/null +++ b/js/src/jit-test/tests/debug/class-01.js @@ -0,0 +1,20 @@ +// |jit-test| error: TypeError + +let g = newGlobal(); +let dbg = Debugger(g); + +let forceException = g.eval(` + (class extends class {} { + // Calling this will throw for using |this| uninitialized. + constructor() { } + }) +`); + +dbg.onExceptionUnwind = function() { + return { + // Force the return of an illegal value. + return: 1 + } +} + +new forceException; diff --git a/js/src/jit-test/tests/debug/class-02.js b/js/src/jit-test/tests/debug/class-02.js new file mode 100644 index 000000000..3821eea26 --- /dev/null +++ b/js/src/jit-test/tests/debug/class-02.js @@ -0,0 +1,20 @@ +// |jit-test| error: TypeError + +let g = newGlobal(); +let dbg = Debugger(g); + +let forceException = g.eval(` + (class extends class {} { + // Calling this will return a primitive immediately. + constructor() { return {}; } + }) +`); + +dbg.onEnterFrame = function() { + return { + // Force the return of an illegal value. + return: 1 + } +} + +new forceException; diff --git a/js/src/jit-test/tests/debug/class-03.js b/js/src/jit-test/tests/debug/class-03.js new file mode 100644 index 000000000..da1a4f434 --- /dev/null +++ b/js/src/jit-test/tests/debug/class-03.js @@ -0,0 +1,23 @@ +// |jit-test| error: TypeError + +let g = newGlobal(); +let dbg = Debugger(g); + +let forceException = g.eval(` + (class extends class {} { + // Calling this will return a primitive immediately. + constructor() { + debugger; + return {}; + } + }) +`); + +dbg.onDebuggerStatement = function() { + return { + // Force the return of an illegal value. + return: 1 + } +} + +new forceException; diff --git a/js/src/jit-test/tests/debug/class-04.js b/js/src/jit-test/tests/debug/class-04.js new file mode 100644 index 000000000..5f772e4d3 --- /dev/null +++ b/js/src/jit-test/tests/debug/class-04.js @@ -0,0 +1,22 @@ +// |jit-test| error: TypeError + +let g = newGlobal(); +let dbg = Debugger(g); + +let forceException = g.eval(` + (class extends class {} { + // Calling this will return a primitive on return. + constructor() { return {}; } + }) +`); + +dbg.onEnterFrame = function(f) { + f.onPop = function() { + return { + // Force the return of an illegal value. + return: 1 + } + } +} + +new forceException; diff --git a/js/src/jit-test/tests/debug/class-05.js b/js/src/jit-test/tests/debug/class-05.js new file mode 100644 index 000000000..4af7c23f8 --- /dev/null +++ b/js/src/jit-test/tests/debug/class-05.js @@ -0,0 +1,31 @@ +// |jit-test| error: TypeError + +let g = newGlobal(); +let dbg = Debugger(g); + +let forceException = g.eval(` + (class extends class {} { + // Calling this will return a primitive immediately. + constructor() { + debugger; + return {}; + } + }) +`); + +let handler = { + hit() { + return { + // Force the return of an illegal value. + return: 1 + } + } +}; + +dbg.onDebuggerStatement = function(frame) { + var line0 = frame.script.getOffsetLocation(frame.offset).lineNumber; + var offs = frame.script.getLineOffsets(line0 + 1); + frame.script.setBreakpoint(offs[0], handler); +} + +new forceException; diff --git a/js/src/jit-test/tests/debug/class-06.js b/js/src/jit-test/tests/debug/class-06.js new file mode 100644 index 000000000..fc6ebdbaa --- /dev/null +++ b/js/src/jit-test/tests/debug/class-06.js @@ -0,0 +1,22 @@ +// |jit-test| error: TypeError + +let g = newGlobal(); +let dbg = Debugger(g); + +let forceException = g.eval(` + (class extends class {} { + // Calling this will return a primitive immediately. + constructor() { return {}; } + }) +`); + +dbg.onEnterFrame = function(f) { + f.onStep = function() { + return { + // Force the return of an illegal value. + return: 1 + } + } +} + +new forceException; diff --git a/js/src/jit-test/tests/debug/class-07.js b/js/src/jit-test/tests/debug/class-07.js new file mode 100644 index 000000000..8613e737b --- /dev/null +++ b/js/src/jit-test/tests/debug/class-07.js @@ -0,0 +1,21 @@ +// |jit-test| error: ReferenceError + +let g = newGlobal(); +let dbg = Debugger(g); + +let forceException = g.eval(` + (class extends class {} { + // Calling this will return a primitive immediately. + constructor() { return {}; } + }) +`); + +dbg.onEnterFrame = function() { + return { + // Force the return undefined, which will throw for returning + // while |this| is still undefined. + return: undefined + } +} +print("break here"); +new forceException; diff --git a/js/src/jit-test/tests/debug/class-08.js b/js/src/jit-test/tests/debug/class-08.js new file mode 100644 index 000000000..89c7ccc22 --- /dev/null +++ b/js/src/jit-test/tests/debug/class-08.js @@ -0,0 +1,13 @@ +let g = newGlobal(); +let dbg = Debugger(g); +dbg.onDebuggerStatement = function() { + // Force the constructor to return undefined, which should be replaced with + // |this| if the latter has been initialized. + return { return: undefined }; +} + +assertEq(g.eval(` + new (class extends class {} { + constructor() { super(); this.foo = 42; debugger; } + }) +`).foo, 42); diff --git a/js/src/jit-test/tests/debug/clear-old-analyses-01.js b/js/src/jit-test/tests/debug/clear-old-analyses-01.js new file mode 100644 index 000000000..b566df17d --- /dev/null +++ b/js/src/jit-test/tests/debug/clear-old-analyses-01.js @@ -0,0 +1,38 @@ +// |jit-test| error:AllDone +// When we enter debug mode in a compartment, we must throw away all +// analyses in that compartment (debug mode affects the results of +// analysis, so they become out of date). This is true even when we would +// otherwise be retaining jit code and its related data structures for +// animation timing. + +if (typeof gcPreserveCode != "function") + throw('AllDone'); + +var g = newGlobal(); +var dbg = new Debugger; + +g.eval("" + + function fib(n) { + var a = 0, b = 1; + while (n-- > 0) + b = b+a, a = b-a; + return b; + }); + +g.fib(20); // Cause g.fib to be jitted. This creates an analysis with + // debug mode off. + +gcPreserveCode(); // Tell the gc to preserve JIT code and analyses by + // default. A recent call to js::NotifyAnimationActivity + // could have a similar effect in real life. + +dbg.addDebuggee(g); // Put g in debug mode. This triggers a GC which must + // clear all analyses. In the original buggy code, we also + // release all of g's scripts' JIT code, leading to a + // recompilation the next time it was called. + +g.fib(20); // Run g.fib again, causing it to be re-jitted. If the + // original analysis is still present, JM will assert, + // because it is not in debug mode. + +throw('AllDone'); diff --git a/js/src/jit-test/tests/debug/clear-old-analyses-02.js b/js/src/jit-test/tests/debug/clear-old-analyses-02.js new file mode 100644 index 000000000..0f8da178c --- /dev/null +++ b/js/src/jit-test/tests/debug/clear-old-analyses-02.js @@ -0,0 +1,39 @@ +// |jit-test| error:AllDone +// When we leave debug mode in a compartment, we must throw away all +// analyses in that compartment (debug mode affects the results of +// analysis, so they become out of date). We cannot skip this step when +// there are debuggee frames on the stack. + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + +g.eval("" + + function fib(n) { + var a = 0, b = 1; + while (n-- > 0) + b = b+a, a = b-a; + return b; + }); + + +// Cause g.fib to be jitted. This creates an analysis with debug mode on. +g.fib(20); + +// Setting a breakpoint in g.f causes us to throw away the jit code, but +// not the analysis. +gw.makeDebuggeeValue(g.fib).script.setBreakpoint(0, { hit: function (f) { } }); + +// Take g out of debug mode, with debuggee code on the stack. In older +// code, this would not trigger a cleansing GC, so the script will +// retain its analysis. +dbg.onDebuggerStatement = function (f) { + dbg.removeDebuggee(g); +}; +g.eval('debugger'); + +// Run g.fib again, causing it to be re-jitted. If the original analysis is +// still present, JM will assert, because it is not in debug mode. +g.fib(20); + +throw('AllDone'); diff --git a/js/src/jit-test/tests/debug/dispatch-01.js b/js/src/jit-test/tests/debug/dispatch-01.js new file mode 100644 index 000000000..223ab8c88 --- /dev/null +++ b/js/src/jit-test/tests/debug/dispatch-01.js @@ -0,0 +1,22 @@ +// Test removing hooks during dispatch. + +var g = newGlobal(); +var log = ''; + +function addDebug(n) { + for (var i = 0; i < n; i++) { + var dbg = new Debugger(g); + dbg.num = i; + dbg.onDebuggerStatement = function (stack) { + log += this.num + ', '; + this.enabled = false; + this.onDebuggerStatement = undefined; + gc(); + }; + } + dbg = null; +} + +addDebug(10); +g.eval("debugger;"); +assertEq(log, '0, 1, 2, 3, 4, 5, 6, 7, 8, 9, '); diff --git a/js/src/jit-test/tests/debug/dispatch-02.js b/js/src/jit-test/tests/debug/dispatch-02.js new file mode 100644 index 000000000..549f68342 --- /dev/null +++ b/js/src/jit-test/tests/debug/dispatch-02.js @@ -0,0 +1,21 @@ +// Disabling a Debugger object causes events to stop being delivered to it +// immediately, even if we're in the middle of dispatching. + +var g = newGlobal(); +var log; + +var arr = []; +for (var i = 0; i < 4; i++) { + arr[i] = new Debugger(g); + arr[i].num = i; + arr[i].onDebuggerStatement = function () { + log += this.num; + // Disable them all. + for (var j = 0; j < arr.length; j++) + arr[j].enabled = false; + }; +} + +log = ''; +g.eval("debugger; debugger;"); +assertEq(log, '0'); diff --git a/js/src/jit-test/tests/debug/execution-observability-01.js b/js/src/jit-test/tests/debug/execution-observability-01.js new file mode 100644 index 000000000..0a05d19c7 --- /dev/null +++ b/js/src/jit-test/tests/debug/execution-observability-01.js @@ -0,0 +1,22 @@ +// For perf reasons we don't recompile all a debuggee global's scripts when +// Debugger no longer needs to observe all execution for that global. Test that +// things don't crash if we try to run a script with a BaselineScript that was +// compiled with debug instrumentation when the global is no longer a debuggee. + +var g = newGlobal(); +var dbg = new Debugger(g); +var counter = 0; +dbg.onDebuggerStatement = function (frame) { + counter++; + if (counter == 15) + dbg.onDebuggerStatement = undefined; +}; + +g.eval("" + function f() { + { + let inner = 42; + debugger; + inner++; + } +}); +g.eval("for (var i = 0; i < 20; i++) f()"); diff --git a/js/src/jit-test/tests/debug/execution-observability-02.js b/js/src/jit-test/tests/debug/execution-observability-02.js new file mode 100644 index 000000000..2f6d65d06 --- /dev/null +++ b/js/src/jit-test/tests/debug/execution-observability-02.js @@ -0,0 +1,15 @@ +// Test that baseline frames are marked as debuggee when resuming from +// throwing. + +var g = newGlobal(); +var dbg = new Debugger(g); + +var hits = 0; +dbg.onEnterFrame = function (f) { hits++; }; + +try { + g.eval("for (c in (function() { yield })()) h"); +} catch (e) { +} + +assertEq(hits, 2); diff --git a/js/src/jit-test/tests/debug/execution-observability-03.js b/js/src/jit-test/tests/debug/execution-observability-03.js new file mode 100644 index 000000000..bc46d46d4 --- /dev/null +++ b/js/src/jit-test/tests/debug/execution-observability-03.js @@ -0,0 +1,17 @@ +// Tests that bare callVMs (in the delprop below) are patched correctly. + +var o = {}; +var global = this; +var p = new Proxy(o, { + "deleteProperty": function (target, key) { + var g = newGlobal(); + g.parent = global; + g.eval("var dbg = new Debugger(parent); dbg.onEnterFrame = function(frame) {};"); + return true; + } +}); +function test() { + for (var i=0; i<100; i++) {} + assertEq(delete p.foo, true); +} +test(); diff --git a/js/src/jit-test/tests/debug/execution-observability-04.js b/js/src/jit-test/tests/debug/execution-observability-04.js new file mode 100644 index 000000000..472820b3f --- /dev/null +++ b/js/src/jit-test/tests/debug/execution-observability-04.js @@ -0,0 +1,21 @@ +// Test that we can do debug mode OSR from the interrupt handler. + +var global = this; +var hits = 0; +setInterruptCallback(function() { + print("Interrupt!"); + hits++; + var g = newGlobal(); + g.parent = global; + g.eval("var dbg = new Debugger(parent); dbg.onEnterFrame = function(frame) {};"); + return true; +}); + +function f(x) { + if (x > 200) + return; + interruptIf(x == 100); + f(x + 1); +} +f(0); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/execution-observability-05.js b/js/src/jit-test/tests/debug/execution-observability-05.js new file mode 100644 index 000000000..ba0a3efa6 --- /dev/null +++ b/js/src/jit-test/tests/debug/execution-observability-05.js @@ -0,0 +1,23 @@ +// Test that we can do debug mode OSR from the interrupt handler through an +// on->off->on cycle. + +var global = this; +var hits = 0; +setInterruptCallback(function() { + print("Interrupt!"); + hits++; + var g = newGlobal(); + g.parent = global; + g.eval("var dbg = new Debugger(parent); dbg.onEnterFrame = function(frame) {};"); + g.eval("dbg.removeDebuggee(parent);"); + return true; +}); + +function f(x) { + if (x > 200) + return; + interruptIf(x == 100); + f(x + 1); +} +f(0); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/execution-observability-06.js b/js/src/jit-test/tests/debug/execution-observability-06.js new file mode 100644 index 000000000..87919ade2 --- /dev/null +++ b/js/src/jit-test/tests/debug/execution-observability-06.js @@ -0,0 +1,24 @@ +// Test that OSR respect debuggeeness. + +var g = newGlobal(); +var dbg = new Debugger(g); + +g.eval("" + function f(c) { + if (c == 0) + return; + if (c == 2) + debugger; + f(c-1); + acc = 0; + for (var i = 0; i < 100; i++) + acc += i; +}); + +var log = ""; +dbg.onDebuggerStatement = function (frame) { + frame.onPop = function f() { log += "p"; } +}; + +g.eval("f(2)"); + +assertEq(log, "p"); diff --git a/js/src/jit-test/tests/debug/gc-01.js b/js/src/jit-test/tests/debug/gc-01.js new file mode 100644 index 000000000..a77aa30ba --- /dev/null +++ b/js/src/jit-test/tests/debug/gc-01.js @@ -0,0 +1,20 @@ +// Debuggers with enabled hooks should not be GC'd even if they are otherwise +// unreachable. + +var g = newGlobal(); +var actual = 0; +var expected = 0; + +function f() { + for (var i = 0; i < 20; i++) { + var dbg = new Debugger(g); + dbg.num = i; + dbg.onDebuggerStatement = function (stack) { actual += this.num; }; + expected += i; + } +} + +f(); +gc(); gc(); gc(); +g.eval("debugger;"); +assertEq(actual, expected); diff --git a/js/src/jit-test/tests/debug/gc-02.js b/js/src/jit-test/tests/debug/gc-02.js new file mode 100644 index 000000000..4055650c7 --- /dev/null +++ b/js/src/jit-test/tests/debug/gc-02.js @@ -0,0 +1,28 @@ +// Dispatching an event to a debugger must keep enough of it gc-alive to avoid +// crashing. + +var g = newGlobal(); +var hits; + +function addDebug() { + // The loop is here to defeat the conservative GC. :-\ + for (var i = 0; i < 4; i++) { + var dbg = new Debugger(g); + dbg.onDebuggerStatement = function (stack) { + hits++; + this.enabled = false; + this.onDebuggerStatement = undefined; + gc(); + }; + if (i > 0) { + dbg.enabled = false; + dbg.onDebuggerStatement = undefined; + dbg = null; + } + } +} + +addDebug(); +hits = 0; +g.eval("debugger;"); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/gc-03.js b/js/src/jit-test/tests/debug/gc-03.js new file mode 100644 index 000000000..773c4ad8a --- /dev/null +++ b/js/src/jit-test/tests/debug/gc-03.js @@ -0,0 +1,24 @@ +// Storing a property on a Debugger.Object protects it from GC as long as the +// referent is alive. + +var g = newGlobal(); +var N = g.N = 3; +var dbg = Debugger(g); + +var i = 0; +dbg.onDebuggerStatement = function (frame) { + frame.arguments[0].id = i++; +}; +g.eval("function f(x) { debugger; }"); +g.eval("var arr = [], j; for (j = 0; j < N; j++) arr[j] = {};"); +g.eval("for (j = 0; j < N; j++) f(arr[j]);"); +assertEq(i, N); + +gc(); gc(); + +i = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.arguments[0].id, i++) +} +g.eval("for (j = 0; j < N; j++) f(arr[j]);"); +assertEq(i, N); diff --git a/js/src/jit-test/tests/debug/gc-04.js b/js/src/jit-test/tests/debug/gc-04.js new file mode 100644 index 000000000..8cc6dfe3f --- /dev/null +++ b/js/src/jit-test/tests/debug/gc-04.js @@ -0,0 +1,25 @@ +// Storing a Debugger.Object as a key in a WeakMap protects it from GC as long as +// the referent is alive. + +var g = newGlobal(); +var N = g.N = 10; +var dbg = Debugger(g); +var cache = new WeakMap; + +var i = 0; +dbg.onDebuggerStatement = function (frame) { + cache.set(frame.arguments[0], i++); +}; +g.eval("function f(x) { debugger; }"); +g.eval("var arr = [], j; for (j = 0; j < N; j++) arr[j] = {};"); +g.eval("for (j = 0; j < N; j++) f(arr[j]);"); +assertEq(i, N); + +gc(); gc(); + +i = 0; +dbg.onDebuggerStatement = function (frame) { + assertEq(cache.get(frame.arguments[0]), i++) +}; +g.eval("for (j = 0; j < N; j++) f(arr[j]);"); +assertEq(i, N); diff --git a/js/src/jit-test/tests/debug/gc-05.js b/js/src/jit-test/tests/debug/gc-05.js new file mode 100644 index 000000000..e31253d75 --- /dev/null +++ b/js/src/jit-test/tests/debug/gc-05.js @@ -0,0 +1,41 @@ +// If a Debugger survives its debuggee, its object cache must still be swept. + +var g2arr = []; // non-debuggee globals +var xarr = []; // debuggee objects + +var N = 4, M = 4; +for (var i = 0; i < N; i++) { + var g1 = newGlobal(); + g1.M = M; + var dbg = new Debugger(g1); + var g2 = g1.eval("newGlobal('same-compartment')"); + g1.x = g2.eval("x = {};"); + + dbg.onDebuggerStatement = function (frame) { xarr.push(frame.eval("x").return); }; + g1.eval("debugger;"); + g2arr.push(g2); + + g1 = null; + gc(); +} + +// At least some of the debuggees have probably been collected at this +// point. It is nondeterministic, though. +assertEq(g2arr.length, N); +assertEq(xarr.length, N); + +// Try to make g2arr[i].eval eventually allocate a new object in the same +// location as a previously gc'd object. If the object caches are not being +// swept, the pointer coincidence will cause a Debugger.Object to be erroneously +// reused. +for (var i = 0; i < N; i++) { + var obj = xarr[i]; + for (j = 0; j < M; j++) { + assertEq(obj instanceof Debugger.Object, true); + g2arr[i].eval("x = x.prop = {};"); + obj = obj.getOwnPropertyDescriptor("prop").value;; + assertEq("seen" in obj, false); + obj.seen = true; + gc(); + } +} diff --git a/js/src/jit-test/tests/debug/gc-06.js b/js/src/jit-test/tests/debug/gc-06.js new file mode 100644 index 000000000..69da384e4 --- /dev/null +++ b/js/src/jit-test/tests/debug/gc-06.js @@ -0,0 +1,6 @@ +// Debugger objects do not keep debuggee globals live. +var dbg = new Debugger; +for (var i = 0; i < 10; i++) + dbg.addDebuggee(newGlobal()); +gc(); +assertEq(dbg.getDebuggees().length < 10, true); diff --git a/js/src/jit-test/tests/debug/gc-07.js b/js/src/jit-test/tests/debug/gc-07.js new file mode 100644 index 000000000..d2de5eab4 --- /dev/null +++ b/js/src/jit-test/tests/debug/gc-07.js @@ -0,0 +1,9 @@ +// Don't assert with dead Debugger.Object and live cross-compartment wrapper of referent. +var g = newGlobal(); +for (var j = 0; j < 4; j++) { + var dbg = new Debugger; + dbg.addDebuggee(g); + dbg.enabled = false; + dbg = null; + gc(); gc(); +} diff --git a/js/src/jit-test/tests/debug/gc-08.js b/js/src/jit-test/tests/debug/gc-08.js new file mode 100644 index 000000000..9999ba6bc --- /dev/null +++ b/js/src/jit-test/tests/debug/gc-08.js @@ -0,0 +1,22 @@ +// Debuggers with enabled onExceptionUnwind hooks should not be GC'd even if +// they are otherwise unreachable. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var actual = 0; +var expected = 0; + +function f() { + for (var i = 0; i < 20; i++) { + var dbg = new Debugger(g); + dbg.num = i; + dbg.onExceptionUnwind = function (stack, exc) { actual += this.num; }; + expected += i; + } +} + +f(); +gc(); +assertThrowsValue(function () { g.eval("throw 'fit';"); }, "fit"); +assertEq(actual, expected); diff --git a/js/src/jit-test/tests/debug/gc-09.2.js b/js/src/jit-test/tests/debug/gc-09.2.js new file mode 100644 index 000000000..f124d4287 --- /dev/null +++ b/js/src/jit-test/tests/debug/gc-09.2.js @@ -0,0 +1,16 @@ +// Bug 717104 - Unreachable debuggee globals should not keep their debuggers +// alive. The loop is to defeat conservative stack scanning; if the same stack +// locations are used each time through the loop, at least three of the +// debuggers should be collected. +// +// This is a slight modification of gc-09.js, which contains a cycle. + +for (var i = 0; i < 4; i++) { + var g = newGlobal(); + var dbg = new Debugger(g); + dbg.onDebuggerStatement = function () { throw "FAIL"; }; + dbg.o = makeFinalizeObserver(); +} + +gc(); +assertEq(finalizeCount() > 0, true); diff --git a/js/src/jit-test/tests/debug/gc-09.js b/js/src/jit-test/tests/debug/gc-09.js new file mode 100644 index 000000000..742d2d95a --- /dev/null +++ b/js/src/jit-test/tests/debug/gc-09.js @@ -0,0 +1,15 @@ +// Bug 717104 - Unreachable debuggee globals should not keep their debuggers +// alive. The loop is to defeat conservative stack scanning; if the same stack +// locations are used each time through the loop, at least three of the +// debuggers should be collected. + +for (var i = 0; i < 4; i++) { + var g = newGlobal(); + var dbg = new Debugger(g); + dbg.onDebuggerStatement = function () { throw "FAIL"; }; + dbg.o = makeFinalizeObserver(); + dbg.loop = g; // make a cycle of strong references with dbg and g +} + +gc(); +assertEq(finalizeCount() > 0, true); diff --git a/js/src/jit-test/tests/debug/gc-compartment-01.js b/js/src/jit-test/tests/debug/gc-compartment-01.js new file mode 100644 index 000000000..a22a73618 --- /dev/null +++ b/js/src/jit-test/tests/debug/gc-compartment-01.js @@ -0,0 +1,6 @@ +// A debugger can survive per-compartment GC. + +var g = newGlobal(); +var dbg = Debugger(g); +gc(g); +gc(this); diff --git a/js/src/jit-test/tests/debug/gc-compartment-02.js b/js/src/jit-test/tests/debug/gc-compartment-02.js new file mode 100644 index 000000000..7eeb14e78 --- /dev/null +++ b/js/src/jit-test/tests/debug/gc-compartment-02.js @@ -0,0 +1,13 @@ +// Referents of Debugger.Objects in other compartments always survive per-compartment GC. + +var g = newGlobal(); +var dbg = Debugger(g); +var arr = []; +dbg.onDebuggerStatement = function (frame) { arr.push(frame.eval("[]").return); }; +g.eval("for (var i = 0; i < 10; i++) debugger;"); +assertEq(arr.length, 10); + +gc(g); + +for (var i = 0; i < arr.length; i++) + assertEq(arr[i].class, "Array"); diff --git a/js/src/jit-test/tests/debug/inspect-wrapped-promise.js b/js/src/jit-test/tests/debug/inspect-wrapped-promise.js new file mode 100644 index 000000000..f27ab4172 --- /dev/null +++ b/js/src/jit-test/tests/debug/inspect-wrapped-promise.js @@ -0,0 +1,88 @@ +if (typeof Promise === "undefined") + quit(0); + +load(libdir + "asserts.js"); + +let g = newGlobal(); +let dbg = new Debugger(); +let gw = dbg.addDebuggee(g); + +g.promise1 = new Promise(() => {}); +g.promise2 = Promise.resolve(42); +g.promise3 = Promise.reject(42); +g.promise4 = new Object(); +g.promise5 = Promise.prototype; + +let promiseDO1 = gw.getOwnPropertyDescriptor('promise1').value; +let promiseDO2 = gw.getOwnPropertyDescriptor('promise2').value; +let promiseDO3 = gw.getOwnPropertyDescriptor('promise3').value; +let promiseDO4 = gw.getOwnPropertyDescriptor('promise4').value; +let promiseDO5 = gw.getOwnPropertyDescriptor('promise5').value; + +assertEq(promiseDO1.isPromise, true); +assertEq(promiseDO2.isPromise, true); +assertEq(promiseDO3.isPromise, true); +assertEq(promiseDO4.isPromise, false); +assertEq(promiseDO5.isPromise, false); + +assertEq(promiseDO1.promiseState, "pending"); +assertEq(promiseDO2.promiseState, "fulfilled"); +assertEq(promiseDO3.promiseState, "rejected"); +assertThrowsInstanceOf(function () { promiseDO4.promiseState }, TypeError); +assertThrowsInstanceOf(function () { promiseDO5.promiseState }, TypeError); + +assertThrowsInstanceOf(function () { promiseDO1.promiseValue }, TypeError); +assertEq(promiseDO2.promiseValue, 42); +assertThrowsInstanceOf(function () { promiseDO3.promiseValue }, TypeError); +assertThrowsInstanceOf(function () { promiseDO4.promiseValue }, TypeError); +assertThrowsInstanceOf(function () { promiseDO5.promiseValue }, TypeError); + +assertThrowsInstanceOf(function () { promiseDO1.promiseReason }, TypeError); +assertThrowsInstanceOf(function () { promiseDO2.promiseReason }, TypeError); +assertEq(promiseDO3.promiseReason, 42); +assertThrowsInstanceOf(function () { promiseDO4.promiseReason }, TypeError); +assertThrowsInstanceOf(function () { promiseDO5.promiseReason }, TypeError); + +// Depending on whether async stacks are activated, this can be null, which +// has typeof null. +assertEq(typeof promiseDO1.promiseAllocationSite === "object", true); +assertEq(typeof promiseDO2.promiseAllocationSite === "object", true); +assertEq(typeof promiseDO3.promiseAllocationSite === "object", true); +assertThrowsInstanceOf(function () { promiseDO4.promiseAllocationSite }, TypeError); +assertThrowsInstanceOf(function () { promiseDO5.promiseAllocationSite }, TypeError); + +// Depending on whether async stacks are activated, this can be null, which +// has typeof null. +assertThrowsInstanceOf(function () { promiseDO1.promiseResolutionSite }, TypeError); +assertEq(typeof promiseDO2.promiseResolutionSite === "object", true); +assertEq(typeof promiseDO3.promiseResolutionSite === "object", true); +assertThrowsInstanceOf(function () { promiseDO4.promiseResolutionSite }, TypeError); +assertThrowsInstanceOf(function () { promiseDO5.promiseResolutionSite }, TypeError); + +assertEq(promiseDO1.promiseID, 1); +assertEq(promiseDO2.promiseID, 2); +assertEq(promiseDO3.promiseID, 3); +assertThrowsInstanceOf(function () { promiseDO4.promiseID }, TypeError); +assertThrowsInstanceOf(function () { promiseDO5.promiseID }, TypeError); + +assertEq(typeof promiseDO1.promiseDependentPromises, "object"); +assertEq(typeof promiseDO2.promiseDependentPromises, "object"); +assertEq(typeof promiseDO3.promiseDependentPromises, "object"); +assertThrowsInstanceOf(function () { promiseDO4.promiseDependentPromises }, TypeError); +assertThrowsInstanceOf(function () { promiseDO5.promiseDependentPromises }, TypeError); + +assertEq(promiseDO1.promiseDependentPromises.length, 0); +assertEq(promiseDO2.promiseDependentPromises.length, 0); +assertEq(promiseDO3.promiseDependentPromises.length, 0); + +assertEq(typeof promiseDO1.promiseLifetime, "number"); +assertEq(typeof promiseDO2.promiseLifetime, "number"); +assertEq(typeof promiseDO3.promiseLifetime, "number"); +assertThrowsInstanceOf(function () { promiseDO4.promiseLifetime }, TypeError); +assertThrowsInstanceOf(function () { promiseDO5.promiseLifetime }, TypeError); + +assertThrowsInstanceOf(function () { promiseDO1.promiseTimeToResolution }, TypeError); +assertEq(typeof promiseDO2.promiseTimeToResolution, "number"); +assertEq(typeof promiseDO3.promiseTimeToResolution, "number"); +assertThrowsInstanceOf(function () { promiseDO4.promiseTimeToResolution }, TypeError); +assertThrowsInstanceOf(function () { promiseDO5.promiseTimeToResolution }, TypeError); diff --git a/js/src/jit-test/tests/debug/makeGlobalObjectReference-01.js b/js/src/jit-test/tests/debug/makeGlobalObjectReference-01.js new file mode 100644 index 000000000..85f958777 --- /dev/null +++ b/js/src/jit-test/tests/debug/makeGlobalObjectReference-01.js @@ -0,0 +1,26 @@ +// Debugger.prototype.makeGlobalObjectReference returns a D.O for a global +// without adding it as a debuggee. + +let g1 = newGlobal(); +let dbg = new Debugger; +assertEq(dbg.hasDebuggee(g1), false); + +let g1w = dbg.makeGlobalObjectReference(g1); +assertEq(dbg.hasDebuggee(g1), false); +assertEq(g1w.unsafeDereference(), g1); +assertEq(g1w, g1w.makeDebuggeeValue(g1)); + +assertEq(dbg.addDebuggee(g1w), g1w); +assertEq(dbg.hasDebuggee(g1), true); +assertEq(dbg.hasDebuggee(g1w), true); +assertEq(g1w.unsafeDereference(), g1); +assertEq(g1w, g1w.makeDebuggeeValue(g1)); + +// makeGlobalObjectReference dereferences CCWs. +let g2 = newGlobal(); +g2.g1 = g1; +let g2w = dbg.addDebuggee(g2); +let g2g1w = g2w.getOwnPropertyDescriptor('g1').value; +assertEq(g2g1w !== g1w, true); +assertEq(g2g1w.unwrap(), g1w); +assertEq(dbg.makeGlobalObjectReference(g2g1w), g1w); diff --git a/js/src/jit-test/tests/debug/makeGlobalObjectReference-02.js b/js/src/jit-test/tests/debug/makeGlobalObjectReference-02.js new file mode 100644 index 000000000..a3db8e45e --- /dev/null +++ b/js/src/jit-test/tests/debug/makeGlobalObjectReference-02.js @@ -0,0 +1,13 @@ +// Debugger.prototype.makeGlobalObjectReference only accepts actual global objects. + +load(libdir + 'asserts.js'); + +var dbg = new Debugger; + +assertThrowsInstanceOf(() => dbg.makeGlobalObjectReference(true), TypeError); +assertThrowsInstanceOf(() => dbg.makeGlobalObjectReference("foo"), TypeError); +assertThrowsInstanceOf(() => dbg.makeGlobalObjectReference(12), TypeError); +assertThrowsInstanceOf(() => dbg.makeGlobalObjectReference(undefined), TypeError); +assertThrowsInstanceOf(() => dbg.makeGlobalObjectReference(null), TypeError); +assertThrowsInstanceOf(() => dbg.makeGlobalObjectReference({ xlerb: "sbot" }), TypeError); +assertEq(dbg.makeGlobalObjectReference(this) instanceof Debugger.Object, true); diff --git a/js/src/jit-test/tests/debug/makeGlobalObjectReference-03.js b/js/src/jit-test/tests/debug/makeGlobalObjectReference-03.js new file mode 100644 index 000000000..4c5685d30 --- /dev/null +++ b/js/src/jit-test/tests/debug/makeGlobalObjectReference-03.js @@ -0,0 +1,8 @@ +// Debugger.prototype.makeGlobalObjectReference should not accept invisible-to-debugger globals. +load(libdir + 'asserts.js'); + +var g = newGlobal({ invisibleToDebugger: true }); + +assertThrowsInstanceOf(function () { + (new Debugger).makeGlobalObjectReference(g) +}, TypeError); diff --git a/js/src/jit-test/tests/debug/noExecute-01.js b/js/src/jit-test/tests/debug/noExecute-01.js new file mode 100644 index 000000000..993342141 --- /dev/null +++ b/js/src/jit-test/tests/debug/noExecute-01.js @@ -0,0 +1,29 @@ +// Tests that NX disallows debuggee execution for all the hooks. + +load(libdir + "asserts.js"); +load(libdir + "debuggerNXHelper.js"); + +var g = newGlobal(); +var dbg = new Debugger(g); + +// Attempts to call g.f without going through an invocation function should +// throw. +g.eval(` + function f() { } + var o = { + get p() { }, + set p(x) { } + }; + `); + +var handlers = [() => { g.f(); }, + () => { g.o.p } , + () => { g.o.p = 42; }]; + +function testHook(hookName) { + for (var h of handlers) { + assertThrowsInstanceOf(h, Debugger.DebuggeeWouldRun); + } +} + +testDebuggerHooksNX(dbg, g, testHook); diff --git a/js/src/jit-test/tests/debug/noExecute-02.js b/js/src/jit-test/tests/debug/noExecute-02.js new file mode 100644 index 000000000..2a8dfef97 --- /dev/null +++ b/js/src/jit-test/tests/debug/noExecute-02.js @@ -0,0 +1,39 @@ +// Tests that invocation functions work. + +load(libdir + "asserts.js"); +load(libdir + "debuggerNXHelper.js"); + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + +g.eval(` + function d() { debugger; } + function f() { return 42; } + var o = { + get p() { return 42; }, + set p(x) { } + }; + `); + +var strs = ["f();", "o.p", "o.p = 42"]; + +var fw; +dbg.onDebuggerStatement = (frame) => { + fw = frame.arguments[0]; +}; +gw.executeInGlobal("d(f)"); +dbg.onDebuggerStatement = undefined; + +function testHook(hookName) { + var newestFrame = dbg.getNewestFrame(); + for (var s of strs) { + if (newestFrame) { + assertEq(newestFrame.eval(s).return, 42); + } + assertEq(gw.executeInGlobal(s).return, 42); + assertEq(fw.apply(null).return, 42); + } +} + +testDebuggerHooksNX(dbg, g, testHook); diff --git a/js/src/jit-test/tests/debug/noExecute-03.js b/js/src/jit-test/tests/debug/noExecute-03.js new file mode 100644 index 000000000..5ee15866f --- /dev/null +++ b/js/src/jit-test/tests/debug/noExecute-03.js @@ -0,0 +1,28 @@ +// Tests that invocation functions work outside of Debugger code. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = new Debugger(); +var gw = dbg.addDebuggee(g); + +g.eval(` + function f() { debugger; return 42; } + function f2() { return 42; } + var o = { + get p() { return 42; }, + set p(x) { } + }; + `); + +var strs = ["f(f2);", "o.p", "o.p = 42"]; + +var f2w; +dbg.onDebuggerStatement = (frame) => { + f2w = frame.arguments[0]; +}; + +for (var s of strs) { + assertEq(gw.executeInGlobal(s).return, 42); +} +assertEq(f2w.apply(null).return, 42); diff --git a/js/src/jit-test/tests/debug/noExecute-04.js b/js/src/jit-test/tests/debug/noExecute-04.js new file mode 100644 index 000000000..f769f04d8 --- /dev/null +++ b/js/src/jit-test/tests/debug/noExecute-04.js @@ -0,0 +1,43 @@ +// Tests that NX works through the enabled toggle and adding/removing the +// global. + +load(libdir + "asserts.js"); +load(libdir + "debuggerNXHelper.js"); + +var g = newGlobal(); +var dbg = new Debugger(g); + +g.eval(` + function f() { } + var o = { + get p() { }, + set p(x) { } + }; + `); + +var handlers = [() => { g.f(); }, + () => { g.o.p } , + () => { g.o.p = 42; }]; + +function testHookEnabled(hookName, trigger) { + for (var h of handlers) { + assertThrowsInstanceOf(h, Debugger.DebuggeeWouldRun); + dbg.enabled = false; + h(); + dbg.enabled = true; + assertThrowsInstanceOf(h, Debugger.DebuggeeWouldRun); + } +} + +function testHookRemoval(hookName, trigger) { + for (var h of handlers) { + assertThrowsInstanceOf(h, Debugger.DebuggeeWouldRun); + dbg.removeDebuggee(g); + h(); + dbg.addDebuggee(g); + assertThrowsInstanceOf(h, Debugger.DebuggeeWouldRun); + } +} + +testDebuggerHooksNX(dbg, g, testHookEnabled); +testDebuggerHooksNX(dbg, g, testHookRemoval); diff --git a/js/src/jit-test/tests/debug/noExecute-05.js b/js/src/jit-test/tests/debug/noExecute-05.js new file mode 100644 index 000000000..93877c958 --- /dev/null +++ b/js/src/jit-test/tests/debug/noExecute-05.js @@ -0,0 +1,43 @@ +// Tests that NX disallows debuggee execution for all debuggees. + +load(libdir + "asserts.js"); +load(libdir + "debuggerNXHelper.js"); + +var g1 = newGlobal(); +var g2 = newGlobal(); +var dbg = new Debugger; + +dbg.addDebuggee(g1); +dbg.addDebuggee(g2); + +g1.eval(` + function f() { } + var o = { + get p() { }, + set p(x) { } + }; + `); + +g2.eval(` + function f() { } + var o = { + get p() { }, + set p(x) { } + }; + `); + +var handlers = [() => { g1.f(); }, + () => { g1.o.p } , + () => { g1.o.p = 42; }, + () => { g2.f(); }, + () => { g2.o.p } , + () => { g2.o.p = 42; } ]; + +function testHook(hookName) { + for (var h of handlers) { + assertThrowsInstanceOf(h, Debugger.DebuggeeWouldRun); + } +} + +testDebuggerHooksNX(dbg, g1, testHook); +testDebuggerHooksNX(dbg, g2, testHook); diff --git a/js/src/jit-test/tests/debug/noExecute-06.js b/js/src/jit-test/tests/debug/noExecute-06.js new file mode 100644 index 000000000..50b0be2c6 --- /dev/null +++ b/js/src/jit-test/tests/debug/noExecute-06.js @@ -0,0 +1,81 @@ +// Tests that NX disallows debuggee execution for multiple debuggers and +// multiple debuggees. + +load(libdir + "asserts.js"); +load(libdir + "debuggerNXHelper.js"); + +var g1 = newGlobal(); +var g2 = newGlobal(); +var dbg1 = new Debugger; +var dbg2 = new Debugger; + +g1w1 = dbg1.addDebuggee(g1); + +g1w2 = dbg2.addDebuggee(g1); +g2w = dbg2.addDebuggee(g2); + +g1.eval(` + function d(f) { debugger; return f; } + function f() { return 42; } + var o = { + get p() { return 42; }, + set p(x) { } + }; + `); + +g2.eval(` + function d(f) { debugger; return f; } + function f() { return 42; } + var o = { + get p() { return 42; }, + set p(x) { } + }; + `); + +var strs = ["f();", "o.p", "o.p = 42"]; + +var fw1; +dbg1.onDebuggerStatement = (frame) => { + fw1 = frame.arguments[0]; +} +g1.eval('d(f)'); +dbg1.onDebuggerStatement = undefined; +var fw2; +dbg2.onDebuggerStatement = (frame) => { + fw2 = frame.arguments[0]; +} +g2.eval('d(f)'); +dbg2.onDebuggerStatement = undefined; + +function testHook(hookName) { + var newestG1Frame = dbg1.getNewestFrame(); + if (hookName != 'onNewGlobalObject' && + hookName != 'onNewScript' && + hookName != 'onNewPromise' && + hookName != 'onPromiseSettled') + { + var newestG2Frame = dbg2.getNewestFrame(); + } + + for (var s of strs) { + // When this hook is called, g1 has been locked twice, so even invocation + // functions do not work. + assertEq(g1w1.executeInGlobal(s).throw.unsafeDereference() instanceof Debugger.DebuggeeWouldRun, true); + assertEq(g1w2.executeInGlobal(s).throw.unsafeDereference() instanceof Debugger.DebuggeeWouldRun, true); + if (newestG1Frame) { + assertEq(newestG1Frame.eval(s).throw.unsafeDereference() instanceof Debugger.DebuggeeWouldRun, true); + } + assertEq(fw1.apply(null).throw.unsafeDereference() instanceof Debugger.DebuggeeWouldRun, true); + + // But g2 has only been locked once and so should work. + assertEq(g2w.executeInGlobal(s).throw, undefined); + if (newestG2Frame) { + assertEq(newestG2Frame.eval(s).throw, undefined); + } + assertEq(fw2.apply(null).return, 42); + } +} + +testDebuggerHooksNX(dbg1, g1, () => { + testDebuggerHooksNX(dbg2, g2, testHook); +}); diff --git a/js/src/jit-test/tests/debug/noExecute-07.js b/js/src/jit-test/tests/debug/noExecute-07.js new file mode 100644 index 000000000..828c26fd0 --- /dev/null +++ b/js/src/jit-test/tests/debug/noExecute-07.js @@ -0,0 +1,36 @@ +// Tests provenance of Debugger.DebuggeeWouldRun errors. + +load(libdir + "asserts.js"); +load(libdir + "debuggerNXHelper.js"); + +var g1 = newGlobal(); +var g2 = newGlobal(); +var g3 = newGlobal(); +var dbg = new Debugger(g1); + +g3.eval(`var dbg = new Debugger`); +var g1w = g3.dbg.addDebuggee(g1); +g3.dbg.addDebuggee(g2); + +g1.eval(`function f() {}`); + +function testHook(hookName) { + // The stack is like so: + // g1 -> dbg (locks g1) -> g2 -> g3.dbg (locks g1 and g2) + // + // The DebuggeeWouldRun error is always allocated in the topmost locked + // Debugger's compartment. + + // If we try to run script in g1 without going through one of g3.dbg's + // invocation functions, we should get an error allocated in + // g3.Debugger.DebuggeeWouldRun. + assertThrowsInstanceOf(() => { g1.eval(`f()`); }, g3.Debugger.DebuggeeWouldRun); + + // If we try to run script in g1 via one of g3.dbg's invocation functions, + // we should get an error allocated in Debugger.DebuggeeWouldRun. + assertEq(g1w.executeInGlobal(`f()`).throw.unsafeDereference() instanceof Debugger.DebuggeeWouldRun, true); +} + +testDebuggerHooksNX(dbg, g1, () => { + testDebuggerHooksNX(g3.dbg, g2, testHook); +}); diff --git a/js/src/jit-test/tests/debug/onDebuggerStatement-01.js b/js/src/jit-test/tests/debug/onDebuggerStatement-01.js new file mode 100644 index 000000000..524072072 --- /dev/null +++ b/js/src/jit-test/tests/debug/onDebuggerStatement-01.js @@ -0,0 +1,7 @@ +var g = newGlobal(); +g.log = ''; + +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (stack) { g.log += '!'; }; +assertEq(g.eval("log += '1'; debugger; log += '2'; 3;"), 3); +assertEq(g.log, '1!2'); diff --git a/js/src/jit-test/tests/debug/onDebuggerStatement-02.js b/js/src/jit-test/tests/debug/onDebuggerStatement-02.js new file mode 100644 index 000000000..3b3598947 --- /dev/null +++ b/js/src/jit-test/tests/debug/onDebuggerStatement-02.js @@ -0,0 +1,22 @@ +// Activity in the debugger compartment should not trigger debug hooks. + +var g = newGlobal(); +var hit = false; + +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (stack) { hit = true; }; + +debugger; +assertEq(hit, false, "raw debugger statement in debugger compartment should not hit"); + +g.f = function () { debugger; }; +g.eval("f();"); +assertEq(hit, false, "debugger statement in debugger compartment function should not hit"); + +g.outerEval = eval; +g.eval("outerEval('debugger;');"); +assertEq(hit, false, "debugger statement in debugger compartment eval code should not hit"); + +var g2 = newGlobal(); +g2.eval("debugger;"); +assertEq(hit, false, "debugger statement in unrelated non-debuggee compartment should not hit"); diff --git a/js/src/jit-test/tests/debug/onDebuggerStatement-03.js b/js/src/jit-test/tests/debug/onDebuggerStatement-03.js new file mode 100644 index 000000000..0a5525efe --- /dev/null +++ b/js/src/jit-test/tests/debug/onDebuggerStatement-03.js @@ -0,0 +1,13 @@ +// A debugger statement in an onDebuggerStatement hook should not reenter. + +var g = newGlobal(); +var calls = 0; + +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (stack) { + calls++; + debugger; +}; + +assertEq(g.eval("debugger; 7;"), 7); +assertEq(calls, 1); diff --git a/js/src/jit-test/tests/debug/onDebuggerStatement-04.js b/js/src/jit-test/tests/debug/onDebuggerStatement-04.js new file mode 100644 index 000000000..1bdaa65de --- /dev/null +++ b/js/src/jit-test/tests/debug/onDebuggerStatement-04.js @@ -0,0 +1,10 @@ +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onDebuggerStatement = function (frame) { + var code = "assertEq(c, 'ok');\n"; + assertEq(frame.evalWithBindings("eval(s)", {s: code, a: 1234}).return, undefined); +}; +g.eval("function first() { return second(); }"); +g.eval("function second() { return eval('third()'); }"); +g.eval("function third() { debugger; }"); +g.evaluate("first();"); diff --git a/js/src/jit-test/tests/debug/onDebuggerStatement-05.js b/js/src/jit-test/tests/debug/onDebuggerStatement-05.js new file mode 100644 index 000000000..0a05d19c7 --- /dev/null +++ b/js/src/jit-test/tests/debug/onDebuggerStatement-05.js @@ -0,0 +1,22 @@ +// For perf reasons we don't recompile all a debuggee global's scripts when +// Debugger no longer needs to observe all execution for that global. Test that +// things don't crash if we try to run a script with a BaselineScript that was +// compiled with debug instrumentation when the global is no longer a debuggee. + +var g = newGlobal(); +var dbg = new Debugger(g); +var counter = 0; +dbg.onDebuggerStatement = function (frame) { + counter++; + if (counter == 15) + dbg.onDebuggerStatement = undefined; +}; + +g.eval("" + function f() { + { + let inner = 42; + debugger; + inner++; + } +}); +g.eval("for (var i = 0; i < 20; i++) f()"); diff --git a/js/src/jit-test/tests/debug/onEnterFrame-01.js b/js/src/jit-test/tests/debug/onEnterFrame-01.js new file mode 100644 index 000000000..dbcce8935 --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-01.js @@ -0,0 +1,29 @@ +// Basic enterFrame hook tests. + +var g = newGlobal(); +var dbg = Debugger(g); +var type; +dbg.onEnterFrame = function (frame) { + try { + assertEq(frame instanceof Debugger.Frame, true); + assertEq(frame.live, true); + type = frame.type; + } catch (exc) { + type = "Exception thrown: " + exc; + } +}; + +function test(f, expected) { + type = undefined; + f(); + assertEq(type, expected); +} + +// eval triggers the hook +test(function () { g.eval("function h() { return 1; }"); }, "eval"); + +// function calls trigger it +test(function () { assertEq(g.h(), 1); }, "call"); + +// global scripts trigger it +test(function () { g.evaluate("var x = 5;"); assertEq(g.x, 5); }, "global"); diff --git a/js/src/jit-test/tests/debug/onEnterFrame-02.js b/js/src/jit-test/tests/debug/onEnterFrame-02.js new file mode 100644 index 000000000..99b2841ca --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-02.js @@ -0,0 +1,22 @@ +// enterFrame test with recursive debuggee function. + +var g = newGlobal(); +var N = g.N = 9; +g.eval("function f(i) { if (i < N) f(i + 1); }"); + +var dbg = Debugger(g); +var arr = []; +dbg.onEnterFrame = function (frame) { + var i; + for (i = 0; i < arr.length; i++) + assertEq(frame !== arr[i], true); + arr[i] = frame; + + // Check that the whole stack is as expected. + var j = i; + for (; frame; frame = frame.older) + assertEq(arr[j--], frame); +}; + +g.f(0); +assertEq(arr.length, N + 1); diff --git a/js/src/jit-test/tests/debug/onEnterFrame-03.js b/js/src/jit-test/tests/debug/onEnterFrame-03.js new file mode 100644 index 000000000..5b6bbb21e --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-03.js @@ -0,0 +1,23 @@ +// frame.eval works in the enterFrame hook. +// It triggers the enterFrame hook again, recursively. (!) + +var g = newGlobal(); +g.a = "."; + +var dbg = Debugger(g); +var nestCount = 0, N = 9; +var log = ""; +dbg.onEnterFrame = function (frame) { + assertEq(frame.type, "eval"); + if (nestCount < N) { + log += '('; + nestCount++; + var a = frame.eval("a").return; + log += a; + nestCount--; + log += ')'; + } +}; + +assertEq(g.eval("a"), "."); +assertEq(log, Array(N + 1).join("(") + Array(N + 1).join(".)")); diff --git a/js/src/jit-test/tests/debug/onEnterFrame-04.js b/js/src/jit-test/tests/debug/onEnterFrame-04.js new file mode 100644 index 000000000..21f1a6cb3 --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-04.js @@ -0,0 +1,50 @@ +// We detect and stop the runaway recursion caused by making onEnterFrame a +// wrapper of a debuggee function. + +// This is all a bit silly. In any reasonable design, both debugger re-entry +// (the second onEnterFrame invocation) and debuggee re-entry (the call to g.f +// from within the debugger, not via a Debugger invocation function) would raise +// errors immediately. We have plans to do so, but in the mean time, we settle +// for at least detecting the recursion. + +load(libdir + 'asserts.js'); + +var g = newGlobal(); +g.eval("function f(frame) { n++; return 42; }"); +g.n = 0; + +var dbg = Debugger(); +var gw = dbg.addDebuggee(g); + +// Register the debuggee function as the onEnterFrame handler. When we first +// call or eval in the debuggee: +// +// - The onEnterFrame call reporting that frame's creation is itself an event +// that must be reported, so we call onEnterFrame again. +// +// - SpiderMonkey detects the out-of-control recursion, and generates a "too +// much recursion" InternalError in the youngest onEnterFrame call. +// +// - We don't catch it, so the onEnterFrame handler call itself throws. +// +// - Since the Debugger doesn't have an uncaughtExceptionHook (it can't; such a +// hook would itself raise a "too much recursion" exception), Spidermonkey +// reports the exception immediately and terminates the debuggee --- which is +// the next-older onEnterFrame call. +// +// - This termination propagates all the way out to the initial attempt to +// create a frame in the debuggee. +dbg.onEnterFrame = g.f; + +// Get a Debugger.Object instance referring to f. +var debuggeeF = gw.makeDebuggeeValue(g.f); + +// Using f.call allows us to catch the termination. +assertEq(debuggeeF.call(), null); + +// We should never actually begin execution of the function. +assertEq(g.n, 0); + +// When an error is reported, the shell usually exits with a nonzero exit code. +// If we get here, the test passed, so override that behavior. +quit(0); diff --git a/js/src/jit-test/tests/debug/onEnterFrame-05.js b/js/src/jit-test/tests/debug/onEnterFrame-05.js new file mode 100644 index 000000000..6fadf3194 --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-05.js @@ -0,0 +1,15 @@ +// The tracejit does not prevent onEnterFrame from being called. + +var g = newGlobal(); +g.eval("function f() { return 1; }\n"); +var N = g.N = 11; +g.eval("function h() {\n" + + " for (var i = 0; i < N; i += f()) {}\n" + + "}"); +g.h(); // record loop + +var dbg = Debugger(g); +var log = ''; +dbg.onEnterFrame = function (frame) { log += frame.callee.name; }; +g.h(); +assertEq(log, 'h' + Array(N + 1).join('f')); diff --git a/js/src/jit-test/tests/debug/onEnterFrame-06.js b/js/src/jit-test/tests/debug/onEnterFrame-06.js new file mode 100644 index 000000000..9d097691c --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-06.js @@ -0,0 +1,19 @@ +// The tracejit does not prevent onEnterFrame from being called after entering +// a debuggee compartment from a non-debuggee compartment. + +var g1 = newGlobal(); +var g2 = newGlobal(); +var dbg = Debugger(g1, g2); +dbg.removeDebuggee(g2); // turn off debug mode in g2 + +g1.eval("function f() { return 1; }\n"); +var N = g1.N = 11; +g1.eval("function h() {\n" + + " for (var i = 0; i < N; i += f()) {}\n" + + "}"); +g1.h(); // record loop + +var log = ''; +dbg.onEnterFrame = function (frame) { log += frame.callee.name; }; +g1.h(); +assertEq(log, 'h' + Array(N + 1).join('f')); diff --git a/js/src/jit-test/tests/debug/onEnterFrame-07.js b/js/src/jit-test/tests/debug/onEnterFrame-07.js new file mode 100644 index 000000000..ce4316bab --- /dev/null +++ b/js/src/jit-test/tests/debug/onEnterFrame-07.js @@ -0,0 +1,15 @@ +var g = newGlobal(); +var dbg = new Debugger(g); +var visibleFrames = 0; +dbg.onEnterFrame = function (frame) { + print("> " + frame.script.url); + visibleFrames++; +} + +g.eval("(" + function iife() { + [1].forEach(function noop() {}); + for (let x of [1]) {} +} + ")()"); + +// 1 for eval, 1 for iife(), 1 for noop() +assertEq(visibleFrames, 3); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-01.js b/js/src/jit-test/tests/debug/onExceptionUnwind-01.js new file mode 100644 index 000000000..7695d0ada --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-01.js @@ -0,0 +1,24 @@ +// Basic onExceptionUnwind hook test. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = Debugger(g); +var hit = false; +dbg.onExceptionUnwind = function (frame, exc) { + // onExceptionUnwind is called multiple times as the stack is unwound. + // Only check the first hit. + assertEq(arguments.length, 2); + assertEq(frame instanceof Debugger.Frame, true); + if (!hit) { + assertEq(exc, 101); + assertEq(frame.type, "call"); + assertEq(frame.callee.name, "f"); + assertEq(frame.older.type, "eval"); + hit = true; + } +}; + +g.eval("function f() { throw 101; }"); +assertThrowsValue(function () { g.eval("f();"); }, 101); +assertEq(hit, true); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-02.js b/js/src/jit-test/tests/debug/onExceptionUnwind-02.js new file mode 100644 index 000000000..3907d5fc5 --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-02.js @@ -0,0 +1,47 @@ +// The onExceptionUnwind hook is called multiple times as the stack unwinds. + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.dbg = null; +g.eval("(" + function () { + dbg = new Debugger(debuggeeGlobal); + dbg.onExceptionUnwind = function (frame, exc) { + assertEq(frame instanceof Debugger.Frame, true); + assertEq(exc instanceof Debugger.Object, true); + var s = '!'; + for (var f = frame; f; f = f.older) + if (f.type === "call") + s += f.callee.name; + s += ', '; + debuggeeGlobal.log += s; + }; + } + ")();"); + +var log; + +function k() { + throw new Error("oops"); // hook call 1 +} + +function j() { + k(); // hook call 2 + log += 'j-unreached, '; +} + +function h() { + j(); // hook call 3 + log += 'h-unreached, '; +} + +function f() { + try { + h(); // hook call 4 + } catch (exc) { + log += 'f-catch'; + } +} + +log = ''; +f(); +g.dbg.enabled = false; +assertEq(log, '!kjhf, !jhf, !hf, !f, f-catch'); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-03.js b/js/src/jit-test/tests/debug/onExceptionUnwind-03.js new file mode 100644 index 000000000..913b1d2fe --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-03.js @@ -0,0 +1,57 @@ +// The onExceptionUnwind hook is called multiple times as the stack unwinds. + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.dbg = null; +g.eval("(" + function () { + dbg = new Debugger(debuggeeGlobal); + dbg.onExceptionUnwind = function (frame, exc) { + assertEq(frame instanceof Debugger.Frame, true); + assertEq(exc instanceof Debugger.Object, true); + var s = '!'; + for (var f = frame; f; f = f.older) + if (f.type === "call") + s += f.callee.name; + s += ', '; + debuggeeGlobal.log += s; + }; + } + ")();"); + +var log; + +function k() { + try { + throw new Error("oops"); // hook call 1 + } finally { + log += 'k-finally, '; + } // hook call 2 +} + +function j() { + k(); // hook call 3 + log += 'j-unreached, '; +} + +function h() { + try { + j(); // hook call 4 + log += 'h-unreached, '; + } catch (exc) { + log += 'h-catch, '; + throw exc; // hook call 5 + } +} + +function f() { + try { + h(); // hook call 6 + } catch (exc) { + log += 'f-catch, '; + } + log += 'f-after, '; +} + +log = ''; +f(); +g.dbg.enabled = false; +assertEq(log, '!kjhf, k-finally, !kjhf, !jhf, !hf, h-catch, !hf, !f, f-catch, f-after, '); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-04.js b/js/src/jit-test/tests/debug/onExceptionUnwind-04.js new file mode 100644 index 000000000..443499a35 --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-04.js @@ -0,0 +1,17 @@ +// onExceptionUnwind is not called for exceptions thrown and handled in the debugger. +var g = newGlobal(); +var dbg = Debugger(g); +g.log = ''; +dbg.onDebuggerStatement = function (frame) { + try { + throw new Error("oops"); + } catch (exc) { + g.log += exc.message; + } +}; +dbg.onExceptionUnwind = function (frame) { + g.log += 'BAD'; +}; + +g.eval("debugger; log += ' ok';"); +assertEq(g.log, 'oops ok'); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-05.js b/js/src/jit-test/tests/debug/onExceptionUnwind-05.js new file mode 100644 index 000000000..d2032dd45 --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-05.js @@ -0,0 +1,12 @@ +// onExceptionUnwind returning undefined does not affect the thrown exception. + +var g = newGlobal(); +g.parent = this; +g.eval("new Debugger(parent).onExceptionUnwind = function () {};"); + +var obj = new Error("oops"); +try { + throw obj; +} catch (exc) { + assertEq(exc, obj); +} diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-06.js b/js/src/jit-test/tests/debug/onExceptionUnwind-06.js new file mode 100644 index 000000000..bab342dd1 --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-06.js @@ -0,0 +1,13 @@ +// onExceptionUnwind assigning to argv[1] does not affect the thrown exception. + +var g = newGlobal(); +g.parent = this; +g.eval("function f(frame, exc) { f2 = function () { return exc; }; exc = 123; }"); +g.eval("new Debugger(parent).onExceptionUnwind = f;"); + +var obj = new Error("oops"); +try { + throw obj; +} catch (exc) { + assertEq(exc, obj); +} diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-07.js b/js/src/jit-test/tests/debug/onExceptionUnwind-07.js new file mode 100644 index 000000000..53d41c298 --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-07.js @@ -0,0 +1,15 @@ +// Unwinding due to uncatchable errors does not trigger onExceptionUnwind. + +var g = newGlobal(); +var dbg = Debugger(g); +var hits = 0; +dbg.onExceptionUnwind = function (frame, value) { hits = 'BAD'; }; +dbg.onDebuggerStatement = function (frame) { + if (hits++ === 0) + assertEq(frame.eval("debugger;"), null); + else + return null; +} + +assertEq(g.eval("debugger; 2"), 2); +assertEq(hits, 2); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-08.js b/js/src/jit-test/tests/debug/onExceptionUnwind-08.js new file mode 100644 index 000000000..0c2752ee2 --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-08.js @@ -0,0 +1,18 @@ +// Ensure that ScriptDebugEpilogue gets called when onExceptionUnwind +// throws an uncaught exception. +var g = newGlobal(); +var dbg = Debugger(g); +var frame; +dbg.onExceptionUnwind = function (f, x) { + frame = f; + assertEq(frame.live, true); + throw 'unhandled'; +}; +dbg.onDebuggerStatement = function(f) { + assertEq(f.eval('throw 42'), null); + assertEq(frame.live, false); +}; +g.eval('debugger'); + +// Don't fail just because we reported an uncaught exception. +quit(0); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-09.js b/js/src/jit-test/tests/debug/onExceptionUnwind-09.js new file mode 100644 index 000000000..eb4755697 --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-09.js @@ -0,0 +1,15 @@ +// Ensure that ScriptDebugEpilogue gets called when onExceptionUnwind +// terminates execution. +var g = newGlobal(); +var dbg = Debugger(g); +var frame; +dbg.onExceptionUnwind = function (f, x) { + frame = f; + assertEq(frame.live, true); + return null; +}; +dbg.onDebuggerStatement = function(f) { + assertEq(f.eval('throw 42'), null); + assertEq(frame.live, false); +}; +g.eval('debugger'); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-10.js b/js/src/jit-test/tests/debug/onExceptionUnwind-10.js new file mode 100644 index 000000000..b5dd59df9 --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-10.js @@ -0,0 +1,16 @@ +// Ensure that ScriptDebugEpilogue gets called when onExceptionUnwind +// terminates execution. +var g = newGlobal(); +var dbg = Debugger(g); +var frame; +dbg.onExceptionUnwind = function (f, x) { + frame = f; + assertEq(frame.type, 'eval'); + assertEq(frame.live, true); + terminate(); +}; +dbg.onDebuggerStatement = function(f) { + assertEq(f.eval('throw 42'), null); + assertEq(frame.live, false); +}; +g.eval('debugger'); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-11.js b/js/src/jit-test/tests/debug/onExceptionUnwind-11.js new file mode 100644 index 000000000..f5eb15df7 --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-11.js @@ -0,0 +1,29 @@ +// Closing legacy generators should not invoke the onExceptionUnwind hook. + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onExceptionUnwind = function (frame, exc) { + log += "ERROR"; + assertEq(0, 1); +}; +g.eval(` +var log = ""; +function f() { + function gen() { + try { + log += "yield"; + yield 3; + yield 4; + } catch(e) { + log += "catch"; + } finally { + log += "finally"; + } + }; + var it = gen(); + assertEq(it.next(), 3); + it.close(); +}; +f(); +`); +assertEq(g.log, "yieldfinally"); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-12.js b/js/src/jit-test/tests/debug/onExceptionUnwind-12.js new file mode 100644 index 000000000..78874ddea --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-12.js @@ -0,0 +1,14 @@ +var g = newGlobal(); +g.parent = this; +g.hits = 0; +g.eval("new Debugger(parent).onExceptionUnwind = function () { hits++; };"); +function f() { + var x = f(); +} +try { + f(); +} catch (e) { + assertEq(e instanceof InternalError, true); +} finally { + assertEq(g.hits, 0); +} diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-13.js b/js/src/jit-test/tests/debug/onExceptionUnwind-13.js new file mode 100644 index 000000000..611bf4c9d --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-13.js @@ -0,0 +1,16 @@ +// |jit-test| error: 4 +// +// Test that we can handle doing debug mode OSR from onExceptionUnwind when +// settling on a pc without a Baseline ICEntry. + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onExceptionUnwind = function () {}; + +g.eval("" + function f(y) { + if (y > 0) { + throw 4; + } +}); +g.eval("f(0)"); +g.eval("f(1)"); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-14.js b/js/src/jit-test/tests/debug/onExceptionUnwind-14.js new file mode 100644 index 000000000..121f5728e --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-14.js @@ -0,0 +1,23 @@ +var g = newGlobal(); +var dbg = new Debugger(g); + +g.eval("" + function f() { + throw 42; +}); + +g.eval("" + function g() { + throw new Error("42"); +}); + +// Call the functions once. This will compile them in Ion under --ion-eager. +g.eval("try { f(); } catch (e) { }"); +g.eval("try { g(); } catch (e) { }"); + +// Now set an onExceptionUnwind hook so that the Ion-compiled functions will +// try to bail out. The tail of the bytecode for f and g looks like 'throw; +// retrval', with 'retrval' being unreachable. Since 'throw' is resumeAfter, +// bailing out for debug mode will attempt to resume at 'retrval'. Test that +// this case is handled. +dbg.onExceptionUnwind = function f() { }; +g.eval("try { f(); } catch (e) { }"); +g.eval("try { g(); } catch (e) { }"); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-15.js b/js/src/jit-test/tests/debug/onExceptionUnwind-15.js new file mode 100644 index 000000000..0c6f46f23 --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-15.js @@ -0,0 +1,25 @@ +// Test that Ion->Baseline in-place debug mode bailout can recover the iterator +// from the snapshot in a for-of loop. + +g = newGlobal(); +g.parent = this; +g.eval("Debugger(parent).onExceptionUnwind=(function() {})"); +function throwInNext() { + yield 1; + yield 2; + yield 3; + throw 42; +} + +function f() { + for (var o of new throwInNext); +} + +var log = ""; +try { + f(); +} catch (e) { + log += e; +} + +assertEq(log, "42"); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-01.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-01.js new file mode 100644 index 000000000..946490edf --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-01.js @@ -0,0 +1,9 @@ +// Check that an onExceptionUnwind hook can force a frame to return a value early. + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onExceptionUnwind = function (frame, exc) { + return { return:"sproon" }; +}; +g.eval("function f() { throw 'ksnife'; }"); +assertEq(g.f(), "sproon"); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-02.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-02.js new file mode 100644 index 000000000..0e2750b0c --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-02.js @@ -0,0 +1,10 @@ +// Check that if an onExceptionUnwind hook forces a constructor frame to +// return a primitive value, it still gets wrapped up in an object. + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onExceptionUnwind = function (frame, exc) { + return { return:"sproon" }; +}; +g.eval("function f() { throw 'ksnife'; }"); +assertEq(typeof new g.f, "object"); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-03.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-03.js new file mode 100644 index 000000000..b507e0f48 --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-03.js @@ -0,0 +1,11 @@ +// Check that an onExceptionUnwind hook can force a frame to throw a different exception. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onExceptionUnwind = function (frame, exc) { + return { throw:"sproon" }; +}; +g.eval("function f() { throw 'ksnife'; }"); +assertThrowsValue(g.f, "sproon"); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-04.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-04.js new file mode 100644 index 000000000..8c195aadc --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-04.js @@ -0,0 +1,17 @@ +// Check that an onExceptionUnwind hook can force a frame to terminate. + +var g = newGlobal(); +var dbg = Debugger(g); +g.eval("function f() { throw 'ksnife'; }"); +var log = ''; +dbg.onDebuggerStatement = function (frame) { + log += 'd1'; + assertEq(frame.eval("f();"), null); + log += 'd2'; +}; +dbg.onExceptionUnwind = function (frame, exc) { + log += 'u'; + return null; +}; +g.eval("debugger;"); +assertEq(log, "d1ud2"); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js new file mode 100644 index 000000000..d4e7e8576 --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js @@ -0,0 +1,130 @@ +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = Debugger(g); + +g.eval(` +async function f() { + return e; +} +`); + +// To continue testing after uncaught exception, remember the exception and +// return normal completeion. +var currentFrame; +var uncaughtException; +dbg.uncaughtExceptionHook = function(e) { + uncaughtException = e; + return { + return: currentFrame.eval("({ done: true, value: 'uncaught' })").return + }; +}; +function testUncaughtException() { + uncaughtException = undefined; + var val = g.eval(` +var val; +f().then(v => { val = v }); +drainJobQueue(); +val; +`); + assertEq(val, "uncaught"); + assertEq(uncaughtException instanceof TypeError, true); +} + +// Just continue +dbg.onExceptionUnwind = function(frame) { + return undefined; +}; +g.eval(` +var E; +f().catch(e => { exc = e }); +drainJobQueue(); +assertEq(exc instanceof ReferenceError, true); +`); + +// Should return object. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: "foo" + }; +}; +testUncaughtException(); + +// The object should have `done` property and `value` property. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({})").return + }; +}; +testUncaughtException(); + +// The object should have `done` property. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ value: 10 })").return + }; +}; +testUncaughtException(); + +// The object should have `value` property. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: true })").return + }; +}; +testUncaughtException(); + +// `done` property should be a boolean value. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: 10, value: 10 })").return + }; +}; +testUncaughtException(); + +// `done` property shouldn't be an accessor. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ get done() { return true; }, value: 10 })").return + }; +}; +testUncaughtException(); + +// `value` property shouldn't be an accessor. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: true, get value() { return 10; } })").return + }; +}; +testUncaughtException(); + +// The object shouldn't be a Proxy. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("new Proxy({ done: true, value: 10 }, {})").return + }; +}; +testUncaughtException(); + +// Correct resumption value. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: true, value: 10 })").return + }; +}; +var val = g.eval(` +var val; +f().then(v => { val = v }); +drainJobQueue(); +val; +`); +assertEq(val, 10); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-generator.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-generator.js new file mode 100644 index 000000000..6239d9e7e --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-generator.js @@ -0,0 +1,117 @@ +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = Debugger(g); + +g.eval(` +function* f() { + e; +} +`); + +// To continue testing after uncaught exception, remember the exception and +// return normal completeion. +var currentFrame; +var uncaughtException; +dbg.uncaughtExceptionHook = function(e) { + uncaughtException = e; + return { + return: currentFrame.eval("({ done: true, value: 'uncaught' })").return + }; +}; +function testUncaughtException() { + uncaughtException = undefined; + var obj = g.eval(`f().next()`); + assertEq(obj.done, true); + assertEq(obj.value, 'uncaught'); + assertEq(uncaughtException instanceof TypeError, true); +} + +// Just continue +dbg.onExceptionUnwind = function(frame) { + return undefined; +}; +assertThrowsInstanceOf(() => g.eval(`f().next();`), g.ReferenceError); + +// Should return object. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: "foo" + }; +}; +testUncaughtException(); + +// The object should have `done` property and `value` property. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({})").return + }; +}; +testUncaughtException(); + +// The object should have `done` property. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ value: 10 })").return + }; +}; +testUncaughtException(); + +// The object should have `value` property. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: true })").return + }; +}; +testUncaughtException(); + +// `done` property should be a boolean value. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: 10, value: 10 })").return + }; +}; +testUncaughtException(); + +// `done` property shouldn't be an accessor. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ get done() { return true; }, value: 10 })").return + }; +}; +testUncaughtException(); + +// `value` property shouldn't be an accessor. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: true, get value() { return 10; } })").return + }; +}; +testUncaughtException(); + +// The object shouldn't be a Proxy. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("new Proxy({ done: true, value: 10 }, {})").return + }; +}; +testUncaughtException(); + +// Correct resumption value. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: true, value: 10 })").return + }; +}; +var obj = g.eval(`f().next()`); +assertEq(obj.done, true); +assertEq(obj.value, 10); diff --git a/js/src/jit-test/tests/debug/onNewScript-01.js b/js/src/jit-test/tests/debug/onNewScript-01.js new file mode 100644 index 000000000..230584859 --- /dev/null +++ b/js/src/jit-test/tests/debug/onNewScript-01.js @@ -0,0 +1,45 @@ +// Basic newScript hook tests. + +var g = newGlobal(); +var dbg = Debugger(g); +var seen = new WeakMap(); +var hits = 0; +dbg.onNewScript = function (s) { + // Exceptions thrown from onNewScript are swept under the rug, but they + // will at least prevent hits from being the expected number. + assertEq(s instanceof Debugger.Script, true); + assertEq(!seen.has(s), true); + seen.set(s, true); + hits++; +}; + +dbg.uncaughtExceptionHook = function () { hits = -999; }; + +// eval code +hits = 0; +assertEq(g.eval("2 + 2"), 4); +assertEq(hits, 1); + +hits = 0; +assertEq(g.eval("eval('2 + 3')"), 5); +assertEq(hits, 2); + +// global code +hits = 0; +g.evaluate("3 + 4"); +assertEq(hits, 1); + +// function code +hits = 0; +var fn = g.Function("a", "return 5 + a;"); +assertEq(hits, 1); +assertEq(fn(8), 13); +assertEq(hits, 1); + +// cloning functions across compartments +fn = g.evaluate("(function(a) { return 5 + a; })"); +var g2 = newGlobal(); +dbg.addDebuggee(g2, dbg); +hits = 0; +g2.clone(fn); +assertEq(hits, 1); diff --git a/js/src/jit-test/tests/debug/onNewScript-02.js b/js/src/jit-test/tests/debug/onNewScript-02.js new file mode 100644 index 000000000..b9f248bd9 --- /dev/null +++ b/js/src/jit-test/tests/debug/onNewScript-02.js @@ -0,0 +1,65 @@ +// Creating a new script with any number of subscripts triggers the newScript hook exactly once. + +var g = newGlobal(); +var dbg = Debugger(g); +var seen = new WeakMap(); +var hits; +dbg.onNewScript = function (s) { + assertEq(s instanceof Debugger.Script, true); + assertEq(!seen.has(s), true); + seen.set(s, true); + hits++; +}; + +dbg.uncaughtExceptionHook = function () { hits = -999; }; + +function test(f) { + hits = 0; + f(); + assertEq(hits, 1); +} + +// eval declaring a function +test(function () { g.eval("function A(m, n) { return m===0?n+1:n===0?A(m-1,1):A(m-1,A(m,n-1)); }"); }); + +// evaluate declaring a function +test(function () { g.eval("function g(a, b) { return b===0?a:g(b,a%b); }"); }); + +// eval declaring multiple functions +test(function () { + g.eval("function e(i) { return i===0||o(i-1); }\n" + + "function o(i) { return i!==0&&e(i-1); }\n"); +}); + +// eval declaring nested functions +test(function () { g.eval("function plus(x) { return function plusx(y) { return x + y; }; }"); }); + +// eval with a function-expression +test(function () { g.eval("[3].map(function (i) { return -i; });"); }); + +// eval with getters and setters +test(function () { g.eval("var obj = {get x() { return 1; }, set x(v) { print(v); }};"); }); + +// Function with nested functions +test(function () { return g.Function("a", "b", "return b - a;"); }); + +// cloning a function with nested functions +test(function () { g.clone(evaluate("(function(x) { return x + 1; })")); }); + +// eval declaring a generator +test(function () { g.eval("function r(n) { for (var i=0;i<n;i++) yield i; }"); }); + +// eval declaring a star generator +test(function () { g.eval("function* sg(n) { for (var i=0;i<n;i++) yield i; }"); }); + +// eval creating several instances of a closure +test(function () { g.eval("for (var i = 0; i < 7; i++)\n" + + " obj = function () { return obj; };\n"); }); + +// non-strict-mode direct eval +g.eval("function e(s) { eval(s); }"); +test(function () { g.e("function f(x) { return -x; }"); }); + +// strict-mode direct eval +g.eval("function E(s) { 'use strict'; eval(s); }"); +test(function () { g.E("function g(x) { return -x; }"); }); diff --git a/js/src/jit-test/tests/debug/onNewScript-03.js b/js/src/jit-test/tests/debug/onNewScript-03.js new file mode 100644 index 000000000..1471c3721 --- /dev/null +++ b/js/src/jit-test/tests/debug/onNewScript-03.js @@ -0,0 +1,7 @@ +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onNewScript = function (s) { + eval(longScript); +} +const longScript = "var x = 1;\n" + new Array(5000).join("x + ") + "x"; +g.eval(longScript); diff --git a/js/src/jit-test/tests/debug/onNewScript-CloneAndExecuteScript.js b/js/src/jit-test/tests/debug/onNewScript-CloneAndExecuteScript.js new file mode 100644 index 000000000..9e56ce920 --- /dev/null +++ b/js/src/jit-test/tests/debug/onNewScript-CloneAndExecuteScript.js @@ -0,0 +1,28 @@ +// Debugger should be notified of scripts created with cloneAndExecuteScript. + +var g = newGlobal(); +var g2 = newGlobal(); +var dbg = new Debugger(g, g2); +var log = ''; + +dbg.onNewScript = function (evalScript) { + log += 'e'; + + dbg.onNewScript = function (clonedScript) { + log += 'c'; + clonedScript.setBreakpoint(0, { + hit(frame) { + log += 'b'; + assertEq(frame.script, clonedScript); + } + }); + }; +}; + +dbg.onDebuggerStatement = function (frame) { + log += 'd'; +}; + +assertEq(log, ''); +g.cloneAndExecuteScript("debugger; // nee", g2); +assertEq(log, 'ecbd'); diff --git a/js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js b/js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js new file mode 100644 index 000000000..7fb6ebf3a --- /dev/null +++ b/js/src/jit-test/tests/debug/onNewScript-ExecuteInGlobalAndReturnScope.js @@ -0,0 +1,32 @@ +// Debugger should be notified of scripts created with ExecuteInGlobalAndReturnScope. + +var g = newGlobal(); +var g2 = newGlobal(); +var dbg = new Debugger(g, g2); +var log = ''; +var canary = 42; + +dbg.onNewScript = function (evalScript) { + log += 'e'; + + dbg.onNewScript = function (clonedScript) { + log += 'c'; + clonedScript.setBreakpoint(0, { + hit(frame) { + log += 'b'; + assertEq(frame.script, clonedScript); + } + }); + }; +}; + +dbg.onDebuggerStatement = function (frame) { + log += 'd'; +}; + +assertEq(log, ''); +var evalScopes = g.evalReturningScope("canary = 'dead'; let lex = 42; debugger; // nee", g2); +assertEq(log, 'ecbd'); +assertEq(canary, 42); +assertEq(evalScopes.vars.canary, 'dead'); +assertEq(evalScopes.lexicals.lex, 42); diff --git a/js/src/jit-test/tests/debug/onNewScript-off-main-thread-01.js b/js/src/jit-test/tests/debug/onNewScript-off-main-thread-01.js new file mode 100644 index 000000000..090c8d6c3 --- /dev/null +++ b/js/src/jit-test/tests/debug/onNewScript-off-main-thread-01.js @@ -0,0 +1,18 @@ +// We still get onNewScript notifications for code compiled off the main thread. + +if (helperThreadCount() === 0) + quit(0); + +var g = newGlobal(); +var dbg = new Debugger(g); + +var log; +dbg.onNewScript = function (s) { + log += 's'; + assertEq(s.source.text, '"t" + "wine"'); +} + +log = ''; +g.offThreadCompileScript('"t" + "wine"'); +assertEq(g.runOffThreadScript(), 'twine'); +assertEq(log, 's'); diff --git a/js/src/jit-test/tests/debug/onNewScript-off-main-thread-02.js b/js/src/jit-test/tests/debug/onNewScript-off-main-thread-02.js new file mode 100644 index 000000000..f4dd96e3f --- /dev/null +++ b/js/src/jit-test/tests/debug/onNewScript-off-main-thread-02.js @@ -0,0 +1,13 @@ +if (helperThreadCount() === 0) + quit(0); + +var global = newGlobal(); +var dbg = new Debugger(global); + +dbg.onNewScript = function (s) { + if (s.url === "<string>") + assertEq(s.getChildScripts().length, 1); +}; + +global.eval('offThreadCompileScript("function inner() { \\\"use asm\\\"; function xxx() {} return xxx; }");'); +global.eval('runOffThreadScript();'); diff --git a/js/src/jit-test/tests/debug/optimized-out-01.js b/js/src/jit-test/tests/debug/optimized-out-01.js new file mode 100644 index 000000000..da5d83efa --- /dev/null +++ b/js/src/jit-test/tests/debug/optimized-out-01.js @@ -0,0 +1,44 @@ +// Tests that we can reflect optimized out values. +// +// Unfortunately these tests are brittle. They depend on opaque JIT heuristics +// kicking in. + +load(libdir + "jitopts.js"); + +if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation)) + quit(0); + +withJitOptions(Opts_Ion2NoOffthreadCompilation, function () { + var g = newGlobal(); + var dbg = new Debugger; + + // Note that this *depends* on CCW scripted functions being opaque to Ion + // optimization and not deoptimizing the frames below the call to toggle. + g.toggle = function toggle(d) { + if (d) { + dbg.addDebuggee(g); + var frame = dbg.getNewestFrame(); + assertEq(frame.implementation, "ion"); + // x is unused and should be elided. + assertEq(frame.environment.getVariable("x").optimizedOut, true); + assertEq(frame.arguments[1].optimizedOut, true); + } + }; + + g.eval("" + function f(d, x) { + "use strict"; + eval("g(d, x)"); // `eval` to avoid inlining g. + }); + + g.eval("" + function g(d, x) { + "use strict"; + for (var i = 0; i < 200; i++); + toggle(d); + }); + + g.eval("(" + function test() { + for (i = 0; i < 5; i++) + f(false, 42); + f(true, 42); + } + ")();"); +}); diff --git a/js/src/jit-test/tests/debug/optimized-out-02.js b/js/src/jit-test/tests/debug/optimized-out-02.js new file mode 100644 index 000000000..cd4b89864 --- /dev/null +++ b/js/src/jit-test/tests/debug/optimized-out-02.js @@ -0,0 +1,38 @@ +// Test that prevUpToDate on frames are cleared. + +var g = newGlobal(); +var dbg = new Debugger(g); + +g.eval(` +function outer(unaliasedArg) { + var unaliasedVar = unaliasedArg + 42; + var aliasedVar = unaliasedArg; + + inner(); + return; + + function inner() { + aliasedVar++; + } +} +`); + +var log = ""; +for (var script of dbg.findScripts()) { + if (script.displayName === "inner") { + script.setBreakpoint(0, { hit: function(frame) { + // Force updateLiveScopes. + var outerEnv = frame.environment; + + // Get the environment of outer's frame on the stack, so that we may + // recover unaliased bindings in the debug scope. + outerEnv = frame.older.environment; + log += outerEnv.getVariable('unaliasedArg'); // 42 + log += outerEnv.getVariable('unaliasedVar'); // 84 + log += outerEnv.getVariable('aliasedVar'); // 42 + }}); + } +} + +g.outer(42); +assertEq(log, "428442"); diff --git a/js/src/jit-test/tests/debug/optimized-out-03.js b/js/src/jit-test/tests/debug/optimized-out-03.js new file mode 100644 index 000000000..e45285dfa --- /dev/null +++ b/js/src/jit-test/tests/debug/optimized-out-03.js @@ -0,0 +1,31 @@ +// Test that eval-in-frame throws on accessing optimized out values. + +load(libdir + "jitopts.js"); + +if (!jitTogglesMatch(Opts_IonEagerNoOffthreadCompilation)) + quit(0); + +withJitOptions(Opts_IonEagerNoOffthreadCompilation, function() { + var dbgGlobal = newGlobal(); + var dbg = new dbgGlobal.Debugger(); + dbg.addDebuggee(this); + + function f() { + assertEq(dbg.getNewestFrame().older.eval("print(a)").throw.unsafeDereference().toString(), + "Error: variable `a' has been optimized out"); + } + + // Test optimized out binding in function scope. + (function () { + function a() {} + for (var i = 0; i < 1; i++) f(); + })(); + + // Test optimized out binding in block scope. + (function () { + { + function a() {} + for (var i = 0; i < 1; i++) f(); + } + })(); +}); diff --git a/js/src/jit-test/tests/debug/prologueFailure-01.js b/js/src/jit-test/tests/debug/prologueFailure-01.js new file mode 100644 index 000000000..1702eeb92 --- /dev/null +++ b/js/src/jit-test/tests/debug/prologueFailure-01.js @@ -0,0 +1,32 @@ +g = newGlobal(); +g.parent = this; + +function installHook() { + let calledTimes = 0; + function hook() { + calledTimes++; + + // Allow the new.target.prototype get to throw. + if (calledTimes === 1) + return undefined; + + return { + return: undefined + }; + } + + Debugger(parent).onExceptionUnwind = hook; +} + + +g.eval("(" + installHook + ")()"); + +var handler = { + get(t, p) { + throw new TypeError; + } +}; + + +var f = new Proxy(function(){}, handler); +new f(); diff --git a/js/src/jit-test/tests/debug/prologueFailure-02.js b/js/src/jit-test/tests/debug/prologueFailure-02.js new file mode 100644 index 000000000..d8c6e38ca --- /dev/null +++ b/js/src/jit-test/tests/debug/prologueFailure-02.js @@ -0,0 +1,49 @@ +g = newGlobal(); +g.parent = this; + +function installHook() { + let calledTimes = 0; + function hook(frame) { + calledTimes++; + switch (calledTimes) { + case 1: + // Proxy get trap + assertEq(frame.type, "call"); + assertEq(frame.script.displayName.includes("get"), true); + break; + case 2: + // wrapper function. There is no entry for notRun + assertEq(frame.type, "call"); + assertEq(frame.script.displayName.includes("wrapper"), true); + break; + case 3: + assertEq(frame.type, "global"); + // Force the top-level to return cleanly, so that we can tell + // assertion failures from the intended throwing. + return { return: undefined }; + + default: + // that's the whole chain. + assertEq(false, true); + } + } + + Debugger(parent).onExceptionUnwind = hook; +} + + +g.eval("(" + installHook + ")()"); + +var handler = { + get(t, p) { + throw new TypeError; + } +}; + +function notRun() {} + +function wrapper() { + var f = new Proxy(notRun, handler); + new f(); +} +wrapper(); diff --git a/js/src/jit-test/tests/debug/prologueFailure-03.js b/js/src/jit-test/tests/debug/prologueFailure-03.js new file mode 100644 index 000000000..c3f8aa018 --- /dev/null +++ b/js/src/jit-test/tests/debug/prologueFailure-03.js @@ -0,0 +1,26 @@ +g = newGlobal(); +g.parent = this; +g.eval("(" + function() { + let calledTimes = 0; + Debugger(parent).onExceptionUnwind = function(frame) { + switch (calledTimes++) { + case 0: + assertEq(frame.older.type, "global"); + break; + case 1: + // Force toplevel to return placidly so that we can tell assertions + // from the throwing in the test. + assertEq(frame.older, null); + return { return: undefined }; + default: + assertEq(false, true); + } + } +} + ")()"); + +var handler = { + get() { + r; + } +}; +new(new Proxy(function() {}, handler)); diff --git a/js/src/jit-test/tests/debug/resumption-01.js b/js/src/jit-test/tests/debug/resumption-01.js new file mode 100644 index 000000000..a2e04edb7 --- /dev/null +++ b/js/src/jit-test/tests/debug/resumption-01.js @@ -0,0 +1,12 @@ +// Simple {throw:} resumption. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (stack) { return {throw: "oops"}; }; + +assertThrowsValue(function () { g.eval("debugger;"); }, "oops"); + +g.eval("function f() { debugger; }"); +assertThrowsValue(function () { g.f(); }, "oops"); diff --git a/js/src/jit-test/tests/debug/resumption-02.js b/js/src/jit-test/tests/debug/resumption-02.js new file mode 100644 index 000000000..8a8481dbb --- /dev/null +++ b/js/src/jit-test/tests/debug/resumption-02.js @@ -0,0 +1,9 @@ +// Simple {return:} resumption. + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onDebuggerStatement = function (stack) { return {return: 1234}; }; + +assertEq(g.eval("debugger; false;"), 1234); +g.eval("function f() { debugger; return 'bad'; }"); +assertEq(g.f(), 1234); diff --git a/js/src/jit-test/tests/debug/resumption-03.js b/js/src/jit-test/tests/debug/resumption-03.js new file mode 100644 index 000000000..cca96a080 --- /dev/null +++ b/js/src/jit-test/tests/debug/resumption-03.js @@ -0,0 +1,35 @@ +// Returning and throwing objects. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.eval("(" + function () { + var how, what; + var dbg = new Debugger(debuggeeGlobal); + dbg.onDebuggerStatement = function (frame) { + if (frame.callee.name === "configure") { + how = frame.arguments[0]; + what = frame.arguments[1]; + } else { + var resume = {}; + resume[how] = what; + return resume; + } + }; + } + ")();"); + +function configure(how, what) { debugger; } +function fire() { debugger; } + +var d = new Date; +configure('return', d); +assertEq(fire(), d); +configure('return', Math); +assertEq(fire(), Math); + +var x = new Error('oh no what are you doing'); +configure('throw', x); +assertThrowsValue(fire, x); +configure('throw', parseInt); +assertThrowsValue(fire, parseInt); diff --git a/js/src/jit-test/tests/debug/resumption-04.js b/js/src/jit-test/tests/debug/resumption-04.js new file mode 100644 index 000000000..1309871ea --- /dev/null +++ b/js/src/jit-test/tests/debug/resumption-04.js @@ -0,0 +1,19 @@ +// |jit-test| error: already executing generator +// Forced return from a generator frame. + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.eval("var dbg = new Debugger(debuggeeGlobal);" + + "dbg.onDebuggerStatement = function () { return {return: '!'}; };"); + +function gen() { + yield '1'; + debugger; // Force return here. The value is ignored. + yield '2'; +} + +var iter = gen(); +assertEq(iter.next(), "1"); +assertEq(iter.next(), "!"); +iter.next(); +assertEq(0, 1); diff --git a/js/src/jit-test/tests/debug/resumption-05.js b/js/src/jit-test/tests/debug/resumption-05.js new file mode 100644 index 000000000..3c790ad32 --- /dev/null +++ b/js/src/jit-test/tests/debug/resumption-05.js @@ -0,0 +1,35 @@ +// null resumption value means terminate the debuggee + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.eval("(" + function () { + var dbg = new Debugger(debuggeeGlobal); + dbg.onDebuggerStatement = function (frame) { + if (frame.callee === null) { + // The first debugger statement below. + debuggeeGlobal.log += "1"; + var cv = frame.eval("f();"); + assertEq(cv, null); + debuggeeGlobal.log += "2"; + } else { + // The second debugger statement. + debuggeeGlobal.log += "3"; + assertEq(frame.callee.name, "f"); + return null; + } + }; + } + ")()"); + +var log = ""; +debugger; + +function f() { + log += "4"; + try { + debugger; // the debugger terminates us here + } finally { + log += "5"; // this should not execute + } +} + +assertEq(log, "1432"); diff --git a/js/src/jit-test/tests/debug/resumption-06.js b/js/src/jit-test/tests/debug/resumption-06.js new file mode 100644 index 000000000..61e63f8ac --- /dev/null +++ b/js/src/jit-test/tests/debug/resumption-06.js @@ -0,0 +1,21 @@ +// |jit-test| error: already executing generator +// Forced return from a star generator frame. + +load(libdir + 'asserts.js') +load(libdir + 'iteration.js') + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.eval("var dbg = new Debugger(debuggeeGlobal);" + + "dbg.onDebuggerStatement = function (frame) { return { return: frame.eval(\"({ done: true, value: '!' })\").return }; };"); + +function* gen() { + yield '1'; + debugger; // Force return here. The value is ignored. + yield '2'; +} +var iter = gen(); +assertIteratorNext(iter, '1'); +assertIteratorDone(iter, '!'); +iter.next(); +assertEq(0, 1); diff --git a/js/src/jit-test/tests/debug/resumption-07.js b/js/src/jit-test/tests/debug/resumption-07.js new file mode 100644 index 000000000..419befdb2 --- /dev/null +++ b/js/src/jit-test/tests/debug/resumption-07.js @@ -0,0 +1,34 @@ +// Return resumption values to non-debuggee frames. + +load(libdir + 'asserts.js'); + +var g = newGlobal(); +var dbg = new Debugger; + +var log; + +function handlerWithResumption(resumption) { + return function (frame) { + log += 'd'; + dbg.removeDebuggee(g); + return resumption; + }; +} + +log = ''; +dbg.onDebuggerStatement = handlerWithResumption(undefined); +dbg.addDebuggee(g); +assertEq(g.eval('debugger; 42;'), 42); +assertEq(log, 'd'); + +log = ''; +dbg.onDebuggerStatement = handlerWithResumption({ return: 1729 }); +dbg.addDebuggee(g); +assertEq(g.eval('debugger; 42;'), 1729); +assertEq(log, 'd'); + +log = ''; +dbg.onDebuggerStatement = handlerWithResumption(null); +dbg.addDebuggee(g); +assertEq(g.evaluate('debugger; 42;', { catchTermination: true }), 'terminated'); +assertEq(log, 'd'); diff --git a/js/src/jit-test/tests/debug/resumption-08.js b/js/src/jit-test/tests/debug/resumption-08.js new file mode 100644 index 000000000..06032ab22 --- /dev/null +++ b/js/src/jit-test/tests/debug/resumption-08.js @@ -0,0 +1,93 @@ +// Check whether we respect resumption values when toggling debug mode on->off +// from various points with live scripts on the stack. + +var g = newGlobal(); +var dbg = new Debugger; + +function reset() { + dbg.onEnterFrame = undefined; + dbg.onDebuggerStatement = undefined; + dbg.addDebuggee(g); + g.eval("(" + function test() { + for (i = 0; i < 5; i++) + f(42); + } + ")();"); +} + +g.eval("" + function f(d) { + return g(d); +}); + +g.eval("" + function g(d) { + debugger; + return d; +}); + +function testResumptionValues(handlerSetter) { + // Test normal return. + reset(); + handlerSetter(undefined); + assertEq(g.eval("(" + function test() { return f(42); } + ")();"), 42); + + // Test forced return. + reset(); + handlerSetter({ return: "not 42" }); + assertEq(g.eval("(" + function test() { return f(42); } + ")();"), "not 42"); + + // Test throw. + reset(); + handlerSetter({ throw: "thrown 42" }); + try { + g.eval("(" + function test() { return f(42); } + ")();");; + } catch (e) { + assertEq(e, "thrown 42"); + } +} + +// Turn off from within the prologue. +testResumptionValues(function (resumptionVal) { + dbg.onEnterFrame = function (frame) { + if (frame.older) { + if (frame.older.older) { + dbg.removeDebuggee(g); + return resumptionVal; + } + } + }; +}); + +// Turn off from within the epilogue. +testResumptionValues(function (resumptionVal) { + dbg.onEnterFrame = function (frame) { + if (frame.older) { + if (frame.older.older) { + frame.onPop = function () { + dbg.removeDebuggee(g); + return resumptionVal; + }; + } + } + }; +}); + +// Turn off from within debugger statement handler. +testResumptionValues(function (resumptionVal) { + dbg.onDebuggerStatement = function (frame) { + dbg.removeDebuggee(g); + return resumptionVal; + }; +}); + +// Turn off from within debug trap handler. +testResumptionValues(function (resumptionVal) { + dbg.onEnterFrame = function (frame) { + if (frame.older) { + if (frame.older.older) { + frame.onStep = function () { + dbg.removeDebuggee(g); + return resumptionVal; + } + } + } + }; +}); diff --git a/js/src/jit-test/tests/debug/resumption-error-01.js b/js/src/jit-test/tests/debug/resumption-error-01.js new file mode 100644 index 000000000..76e2717ce --- /dev/null +++ b/js/src/jit-test/tests/debug/resumption-error-01.js @@ -0,0 +1,7 @@ +// A resumption value can't have both {return:} and {throw:} properties. + +var g = newGlobal(); +var dbg = Debugger(g); +dbg.onDebuggerStatement = stack => ({return: 1, throw: 2}); +dbg.uncaughtExceptionHook = exc => ({return: "corrected"}); +assertEq(g.eval("debugger; false;"), "corrected"); diff --git a/js/src/jit-test/tests/debug/resumption-error-02.js b/js/src/jit-test/tests/debug/resumption-error-02.js new file mode 100644 index 000000000..be5bf54b9 --- /dev/null +++ b/js/src/jit-test/tests/debug/resumption-error-02.js @@ -0,0 +1,16 @@ +// Error handling if parsing a resumption value throws. + +var g = newGlobal(); +var dbg = Debugger(g); +var rv; +dbg.onDebuggerStatement = stack => rv; +dbg.uncaughtExceptionHook = function (exc) { + assertEq(exc, "BANG"); + return {return: "recovered"}; +}; + +rv = {get throw() { throw "BANG"; }}; +assertEq(g.eval("debugger; false;"), "recovered"); + +rv = new Proxy({}, {has() { throw "BANG"; }}); +assertEq(g.eval("debugger; false;"), "recovered"); diff --git a/js/src/jit-test/tests/debug/surfaces-01.js b/js/src/jit-test/tests/debug/surfaces-01.js new file mode 100644 index 000000000..0907916a4 --- /dev/null +++ b/js/src/jit-test/tests/debug/surfaces-01.js @@ -0,0 +1,17 @@ +// Check superficial characteristics of functions and properties (not functionality). + +function checkFunction(obj, name, nargs) { + var desc = Object.getOwnPropertyDescriptor(obj, name); + assertEq(desc.configurable, true, name + " should be configurable"); + assertEq(desc.writable, true, name + " should be writable"); + assertEq(desc.enumerable, false, name + " should be non-enumerable"); + assertEq(desc.value, obj[name]); // well obviously + assertEq(typeof desc.value, 'function', name + " should be a function"); + assertEq(desc.value.length, nargs, name + " should have .length === " + nargs); +} + +checkFunction(this, "Debugger", 1); + +assertEq(Debugger.prototype.constructor, Debugger); +assertEq(Object.prototype.toString.call(Debugger.prototype), "[object Debugger]"); +assertEq(Object.getPrototypeOf(Debugger.prototype), Object.prototype); diff --git a/js/src/jit-test/tests/debug/surfaces-02.js b/js/src/jit-test/tests/debug/surfaces-02.js new file mode 100644 index 000000000..f656fdd3a --- /dev/null +++ b/js/src/jit-test/tests/debug/surfaces-02.js @@ -0,0 +1,31 @@ +// Debugger.prototype.onDebuggerStatement + +load(libdir + 'asserts.js'); + +var g = newGlobal(); +var dbg = new Debugger(g); +gc(); // don't assert marking debug hooks +assertEq(dbg.onDebuggerStatement, undefined); + +function f() {} + +assertThrowsInstanceOf(function () { dbg.onDebuggerStatement = null; }, TypeError); +assertThrowsInstanceOf(function () { dbg.onDebuggerStatement = "bad"; }, TypeError); +assertThrowsInstanceOf(function () { dbg.onDebuggerStatement = {}; }, TypeError); +dbg.onDebuggerStatement = f; +assertEq(dbg.onDebuggerStatement, f); + +assertEq(Object.getOwnPropertyNames(dbg).length, 0); +var desc = Object.getOwnPropertyDescriptor(Debugger.prototype, "onDebuggerStatement"); +assertEq(desc.configurable, true); +assertEq(desc.enumerable, false); + +assertThrowsInstanceOf(function () { desc.get(); }, TypeError); +assertThrowsInstanceOf(function () { desc.get.call(undefined); }, TypeError); +assertThrowsInstanceOf(function () { desc.get.call(Debugger.prototype); }, TypeError); +assertEq(desc.get.call(dbg), f); + +assertThrowsInstanceOf(function () { desc.set(); }, TypeError); +assertThrowsInstanceOf(function () { desc.set.call(dbg); }, TypeError); +assertThrowsInstanceOf(function () { desc.set.call({}, f); }, TypeError); +assertThrowsInstanceOf(function () { desc.set.call(Debugger.prototype, f); }, TypeError); diff --git a/js/src/jit-test/tests/debug/surfaces-03.js b/js/src/jit-test/tests/debug/surfaces-03.js new file mode 100644 index 000000000..bbc11e122 --- /dev/null +++ b/js/src/jit-test/tests/debug/surfaces-03.js @@ -0,0 +1,19 @@ +// dumb basics of uncaughtExceptionHook + +load(libdir + 'asserts.js'); + +var desc = Object.getOwnPropertyDescriptor(Debugger.prototype, "uncaughtExceptionHook"); +assertEq(typeof desc.get, 'function'); +assertEq(typeof desc.set, 'function'); + +assertThrowsInstanceOf(function () { Debugger.prototype.uncaughtExceptionHook = null; }, TypeError); + +var g = newGlobal(); +var dbg = new Debugger(g); +assertEq(desc.get.call(dbg), null); +assertThrowsInstanceOf(function () { dbg.uncaughtExceptionHook = []; }, TypeError); +assertThrowsInstanceOf(function () { dbg.uncaughtExceptionHook = 3; }, TypeError); +dbg.uncaughtExceptionHook = Math.sin; +assertEq(dbg.uncaughtExceptionHook, Math.sin); +dbg.uncaughtExceptionHook = null; +assertEq(dbg.uncaughtExceptionHook, null); diff --git a/js/src/jit-test/tests/debug/surfaces-offsets.js b/js/src/jit-test/tests/debug/surfaces-offsets.js new file mode 100644 index 000000000..1c696293f --- /dev/null +++ b/js/src/jit-test/tests/debug/surfaces-offsets.js @@ -0,0 +1,37 @@ +// Invalid offsets result in exceptions, not bogus results. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = Debugger(g); +var hits; +dbg.onDebuggerStatement = function (frame) { + assertEq(frame.script.getOffsetLocation(frame.offset).lineNumber, g.line); + assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(String(frame.offset)).lineNumber; }, Error); + assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(Object(frame.offset)).lineNumber; }, Error); + + assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(-1).lineNumber; }, Error); + assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(1000000).lineNumber; }, Error); + assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(0.25).lineNumber; }, Error); + assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(+Infinity).lineNumber; }, Error); + assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(-Infinity).lineNumber; }, Error); + assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(NaN).lineNumber; }, Error); + + assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(false).lineNumber; }, Error); + assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(true).lineNumber; }, Error); + assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(undefined).lineNumber; }, Error); + assertThrowsInstanceOf(function () { frame.script.getOffsetLocation().lineNumber; }, Error); + + // We assume that at least one whole number between 0 and frame.offset is invalid. + assertThrowsInstanceOf( + function () { + for (var i = 0; i < frame.offset; i++) + frame.script.getOffsetLocation(i).lineNumber; + }, + Error); + + hits++; +}; + +hits = 0; +g.eval("var line = new Error().lineNumber; debugger;"); diff --git a/js/src/jit-test/tests/debug/testEarlyReturnOnCall.js b/js/src/jit-test/tests/debug/testEarlyReturnOnCall.js new file mode 100644 index 000000000..43472fc7f --- /dev/null +++ b/js/src/jit-test/tests/debug/testEarlyReturnOnCall.js @@ -0,0 +1,24 @@ +var g = newGlobal(); +g.eval("var success = false"); +g.eval("function ponies() {}"); +g.eval("function foo() { ponies(); success = false }"); + +var dbg = new Debugger(g); +dbg.onEnterFrame = function(frame) { + // The goal here is force an early return on the 'call' instruction, + // which should be the 3rd step (callgname, undefined, call) + var step = 0; + frame.onStep = function() { + ++step; + if (step == 2) { + g.success = true; + return; + } + if (step == 3) + return { return: undefined } + } + frame.onPop = function() { new Error(); /* boom */ } +} + +g.foo(); +assertEq(g.success, true); diff --git a/js/src/jit-test/tests/debug/uncaughtExceptionHook-01.js b/js/src/jit-test/tests/debug/uncaughtExceptionHook-01.js new file mode 100644 index 000000000..5be42dd7c --- /dev/null +++ b/js/src/jit-test/tests/debug/uncaughtExceptionHook-01.js @@ -0,0 +1,19 @@ +// Uncaught exceptions in the debugger itself are delivered to the +// uncaughtExceptionHook. + +var g = newGlobal(); +var dbg = new Debugger(g); +var log; +dbg.onDebuggerStatement = function () { + log += 'x'; + throw new TypeError("fail"); +}; +dbg.uncaughtExceptionHook = function (exc) { + assertEq(this, dbg); + assertEq(exc instanceof TypeError, true); + log += '!'; +}; + +log = ''; +g.eval("debugger"); +assertEq(log, 'x!'); diff --git a/js/src/jit-test/tests/debug/uncaughtExceptionHook-02.js b/js/src/jit-test/tests/debug/uncaughtExceptionHook-02.js new file mode 100644 index 000000000..e660c9a18 --- /dev/null +++ b/js/src/jit-test/tests/debug/uncaughtExceptionHook-02.js @@ -0,0 +1,12 @@ +// Returning a bad resumption value causes an exception that is reported to the +// uncaughtExceptionHook. + +var g = newGlobal(); +var dbg = new Debugger(g); +dbg.onDebuggerStatement = function () { return {oops: "bad resumption value"}; }; +dbg.uncaughtExceptionHook = function (exc) { + assertEq(exc instanceof TypeError, true); + return {return: "pass"}; +}; + +assertEq(g.eval("debugger"), "pass"); diff --git a/js/src/jit-test/tests/debug/uncaughtExceptionHook-03.js b/js/src/jit-test/tests/debug/uncaughtExceptionHook-03.js new file mode 100644 index 000000000..d9dc50676 --- /dev/null +++ b/js/src/jit-test/tests/debug/uncaughtExceptionHook-03.js @@ -0,0 +1,34 @@ +// |jit-test| error: ReferenceError +// If uncaughtExceptionHook is absent, the debuggee is terminated. + +var g = newGlobal(); +g.debuggeeGlobal = this; +g.eval("(" + function () { + var dbg = Debugger(debuggeeGlobal); + dbg.onDebuggerStatement = function (frame) { + if (frame.callee === null) { + debuggeeGlobal.log += '1'; + var cv = frame.eval("f();"); + debuggeeGlobal.log += '2'; + assertEq(cv, null); + } else { + assertEq(frame.callee.name, "f"); + debuggeeGlobal.log += '3'; + throw new ReferenceError("oops"); + } + }; + } + ")();"); + +function onerror(msg) { +} + +var log = ''; +debugger; +function f() { + try { + debugger; + } finally { + log += 'x'; + } +} +assertEq(log, '132'); diff --git a/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-01.js b/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-01.js new file mode 100644 index 000000000..b31c0972a --- /dev/null +++ b/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-01.js @@ -0,0 +1,25 @@ +// uncaughtExceptionHook returns a resumption value. + +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = new Debugger(g); +var rv; +dbg.onDebuggerStatement = function () { throw 15; }; +dbg.uncaughtExceptionHook = function (exc) { + assertEq(exc, 15); + return rv; +}; + +// case 1: undefined +rv = undefined; +g.eval("debugger"); + +// case 2: throw +rv = {throw: 57}; +var result; +assertThrowsValue(function () { g.eval("debugger"); }, 57); + +// case 3: return +rv = {return: 42}; +assertEq(g.eval("debugger;"), 42); diff --git a/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-02.js b/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-02.js new file mode 100644 index 000000000..a7ffeaf8f --- /dev/null +++ b/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-02.js @@ -0,0 +1,25 @@ +// uncaughtExceptionHook resumption value other than undefined causes further +// hooks to be skipped. + +var g = newGlobal(); +var log; + +function makeDebug(g, name) { + var dbg = new Debugger(g); + dbg.onDebuggerStatement = function (frame) { + log += name; + throw new Error(name); + }; + dbg.uncaughtExceptionHook = function (exc) { + assertEq(exc.message, name); + return name == "2" ? {return: 42} : undefined; + }; +} + +var arr = []; +for (var i = 0; i < 6; i++) + arr[i] = makeDebug(g, "" + i); + +log = ''; +assertEq(g.eval("debugger;"), 42); +assertEq(log, "012"); diff --git a/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-03.js b/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-03.js new file mode 100644 index 000000000..85240f5d9 --- /dev/null +++ b/js/src/jit-test/tests/debug/uncaughtExceptionHook-resumption-03.js @@ -0,0 +1,12 @@ +// After an onExceptionUnwind hook throws, if uncaughtExceptionHook returns +// undefined, the original exception continues to propagate. + +var g = newGlobal(); +var dbg = new Debugger(g); +var log = ''; +dbg.onExceptionUnwind = function () { log += "1"; throw new Error("oops"); }; +dbg.uncaughtExceptionHook = function () { log += "2"; }; + +g.eval("var x = new Error('oops');"); +g.eval("try { throw x; } catch (exc) { assertEq(exc, x); }"); +assertEq(log, "12"); diff --git a/js/src/jit-test/tests/debug/wasm-01.js b/js/src/jit-test/tests/debug/wasm-01.js new file mode 100644 index 000000000..1b2908d0f --- /dev/null +++ b/js/src/jit-test/tests/debug/wasm-01.js @@ -0,0 +1,33 @@ +// Tests that wasm module scripts are available via findScripts. + +if (!wasmIsSupported()) + quit(); + +var g = newGlobal(); +g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" 0))')));`); + +function isWasm(script) { return script.format === "wasm"; } + +var dbg = new Debugger(g); +var foundScripts1 = dbg.findScripts().filter(isWasm); +assertEq(foundScripts1.length, 1); +var found = foundScripts1[0]; + +// Add another module, we should be able to find it via findScripts. +g.eval(`o2 = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "a" 0))')));`); +var foundScripts2 = dbg.findScripts().filter(isWasm); +assertEq(foundScripts2.length, 2); + +// The first module should be in the list as wrapping the same wasm module +// twice gets the same Debugger.Script. +assertEq(foundScripts2.indexOf(found) !== -1, true); + +// The two modules are distinct. +assertEq(foundScripts2[0] !== foundScripts2[1], true); + +// We should be able to find the same script via its source. +for (var ws of foundScripts2) { + var scriptsFromSource = dbg.findScripts({ source: ws.source }); + assertEq(scriptsFromSource.length, 1); + assertEq(scriptsFromSource[0], ws); +} diff --git a/js/src/jit-test/tests/debug/wasm-02.js b/js/src/jit-test/tests/debug/wasm-02.js new file mode 100644 index 000000000..e6b591b58 --- /dev/null +++ b/js/src/jit-test/tests/debug/wasm-02.js @@ -0,0 +1,22 @@ +// Tests that wasm module scripts are available via onNewScript. + +if (!wasmIsSupported()) + quit(); + +var g = newGlobal(); +var dbg = new Debugger(g); + +var gotScript; +dbg.onNewScript = (script) => { + gotScript = script; +} + +g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" 0))')));`); +assertEq(gotScript.format, "wasm"); + +var gotScript2 = gotScript; +g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "a" 0))')));`); +assertEq(gotScript.format, "wasm"); + +// The two wasm Debugger.Scripts are distinct. +assertEq(gotScript !== gotScript2, true); diff --git a/js/src/jit-test/tests/debug/wasm-03.js b/js/src/jit-test/tests/debug/wasm-03.js new file mode 100644 index 000000000..03e96bbf1 --- /dev/null +++ b/js/src/jit-test/tests/debug/wasm-03.js @@ -0,0 +1,36 @@ +// Tests that wasm module scripts have synthesized sources. + +load(libdir + "asserts.js"); + +if (!wasmIsSupported()) + quit(); + +var g = newGlobal(); +var dbg = new Debugger(g); + +var s; +dbg.onNewScript = (script) => { + s = script; +} + +g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" 0))')));`); +assertEq(s.format, "wasm"); + +var source = s.source; + +assertEq(s.source, source); +assertEq(source.introductionType, "wasm"); +assertEq(source.introductionScript, s); +// Wasm sources shouldn't be considered source mapped. +assertEq(!source.sourceMapURL, true); +assertThrowsInstanceOf(() => source.sourceMapURL = 'foo', Error); +// We must have some text. +assertEq(!!source.text, true); + +// TODOshu: Wasm is moving very fast and what we return for these values is +// currently not interesting to test. Instead, test that they do not throw. +source.url; +source.element; +source.displayURL; +source.introductionOffset; +source.elementAttributeName; diff --git a/js/src/jit-test/tests/debug/wasm-04.js b/js/src/jit-test/tests/debug/wasm-04.js new file mode 100644 index 000000000..4fb9cd7d6 --- /dev/null +++ b/js/src/jit-test/tests/debug/wasm-04.js @@ -0,0 +1,34 @@ +// Tests that wasm module scripts throw for everything except text. + +load(libdir + "asserts.js"); + +if (!wasmIsSupported()) + quit(); + +var g = newGlobal(); +var dbg = new Debugger(g); + +var s; +dbg.onNewScript = (script) => { + s = script; +} + +g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" 0))')));`); +assertEq(s.format, "wasm"); + +assertThrowsInstanceOf(() => s.displayName, Error); +assertThrowsInstanceOf(() => s.url, Error); +assertThrowsInstanceOf(() => s.startLine, Error); +assertThrowsInstanceOf(() => s.lineCount, Error); +assertThrowsInstanceOf(() => s.sourceStart, Error); +assertThrowsInstanceOf(() => s.sourceLength, Error); +assertThrowsInstanceOf(() => s.global, Error); +assertThrowsInstanceOf(() => s.getChildScripts(), Error); +assertThrowsInstanceOf(() => s.getAllOffsets(), Error); +assertThrowsInstanceOf(() => s.getAllColumnOffsets(), Error); +assertThrowsInstanceOf(() => s.setBreakpoint(0, { hit: () => {} }), Error); +assertThrowsInstanceOf(() => s.getBreakpoint(0), Error); +assertThrowsInstanceOf(() => s.clearBreakpoint({}), Error); +assertThrowsInstanceOf(() => s.clearAllBreakpoints(), Error); +assertThrowsInstanceOf(() => s.isInCatchScope(0), Error); +assertThrowsInstanceOf(() => s.getOffsetsCoverage(), Error); diff --git a/js/src/jit-test/tests/debug/wasm-05.js b/js/src/jit-test/tests/debug/wasm-05.js new file mode 100644 index 000000000..e0262abe4 --- /dev/null +++ b/js/src/jit-test/tests/debug/wasm-05.js @@ -0,0 +1,38 @@ +// Tests that wasm module scripts have text line to bytecode offset information +// when source text is generated. + +load(libdir + "asserts.js"); + +// Disabled in aurora (see also bug 1326452). +quit(); + +// Checking if experimental format generates internal source map to binary file +// by querying debugger scripts getLineOffsets. +// (Notice that the source map will not be produced by wasmBinaryToText) +function getAllOffsets(wast) { + var sandbox = newGlobal(''); + var dbg = new Debugger(); + dbg.addDebuggee(sandbox); + sandbox.eval(` + var wasm = wasmTextToBinary('${wast}'); + var m = new WebAssembly.Instance(new WebAssembly.Module(wasm)); + `); + var wasmScript = dbg.findScripts().filter(s => s.format == 'wasm')[0]; + var lines = wasmScript.source.text.split('\n'); + return lines.map((l, n) => { return { str: l, offsets: wasmScript.getLineOffsets(n + 1) }; }); +} + +var result1 = getAllOffsets('(module \ + (func (nop)) \ + (func (drop (f32.sqrt (f32.add (f32.const 1.0) (f32.const 2.0))))) \ +)'); + +var nopLine = result1.filter(i => i.str.indexOf('nop') >= 0); +assertEq(nopLine.length, 1); +// The nopLine shall have single offset. +assertEq(nopLine[0].offsets.length, 1); +assertEq(nopLine[0].offsets[0] > 0, true); + +var singleOffsetLines = result1.filter(i => i.offsets.length === 1); +// There shall be total 6 lines with single offset. +assertEq(singleOffsetLines.length === 6, true); |