diff --git a/js/src/jit-test/jit_test.py b/js/src/jit-test/jit_test.py index 60e50ef95386287450a242333b75b82f3407ff92..1501bed53118dd2b8b2900a40e3d85da1200c165 100755 --- a/js/src/jit-test/jit_test.py +++ b/js/src/jit-test/jit_test.py @@ -132,7 +132,11 @@ def get_test_cmd(path, jitflags, lib_dir, shell_args): libdir_var = lib_dir if not libdir_var.endswith('/'): libdir_var += '/' - expr = "const platform=%r; const libdir=%r;"%(sys.platform, libdir_var) + scriptdir_var = os.path.dirname(path); + if not scriptdir_var.endswith('/'): + scriptdir_var += '/' + expr = ("const platform=%r; const libdir=%r; const scriptdir=%r" + % (sys.platform, libdir_var, scriptdir_var)) # We may have specified '-a' or '-d' twice: once via --jitflags, once # via the "|jit-test|" line. Remove dups because they are toggles. return ([ JS ] + list(set(jitflags)) + shell_args + 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 0000000000000000000000000000000000000000..e2df9aaf066585c60e7f1af6bd853ff742f467b5 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-07.js @@ -0,0 +1,31 @@ +// findScripts can filter scripts by global. +var g1 = newGlobal('new-compartment'); +var g2 = newGlobal('new-compartment'); +var g3 = newGlobal('new-compartment'); + +var dbg = new Debugger(g1, g2); + +g1.eval('function f() {}'); +g2.eval('function g() {}'); +g2.eval('function h() {}'); +var g1fw = dbg.addDebuggee(g1.f); +var g2gw = dbg.addDebuggee(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 0000000000000000000000000000000000000000..40b3aafff5bf78e0df304b32795d76857fe8e082 --- /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 0000000000000000000000000000000000000000..13867361be0b4900b1ba250604db356d4462c5ec --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-08.js @@ -0,0 +1,78 @@ +// Debugger.prototype.findScripts can filter scripts by URL. +var g1 = newGlobal('new-compartment'); +var g2 = newGlobal('new-compartment'); +var g3 = newGlobal('new-compartment'); + +// 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(g1, g2, g3); + +var g1fw = dbg.addDebuggee(g1.g1f); +var g1gw = dbg.addDebuggee(g1.g1g); +var g2fw = dbg.addDebuggee(g2.g2f); +var g2gw = dbg.addDebuggee(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 0000000000000000000000000000000000000000..0b359681b1f22cef8f28731950f2e6c156843389 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-09.js @@ -0,0 +1,44 @@ +// Passing bad query properties to Debugger.prototype.findScripts throws. +load(libdir + 'asserts.js'); + +var dbg = new Debugger(); +assertEq(dbg.findScripts().length, 0); +assertEq(dbg.findScripts({}).length, 0); + +assertEq(dbg.findScripts({global:{}}).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 0000000000000000000000000000000000000000..0fa6b0fae706b304abf009cbe211d4b6214ffc0e --- /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('new-compartment'); +g1.eval('function f(){}'); + +var g2 = newGlobal('new-compartment'); +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 0000000000000000000000000000000000000000..7a170645e82c9b65503b450ac11d9231d4de32c5 --- /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 0000000000000000000000000000000000000000..4dfc96a3fbf917ff0c08d17a996c163cb77a01cb --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-11.js @@ -0,0 +1,35 @@ +// Debugger.prototype.findScripts can filter scripts by line number. +var g = newGlobal('new-compartment'); +var dbg = new Debugger(g); + +var scriptname = scriptdir + 'Debugger-findScripts-11-script2'; +g.load(scriptname); + +var gfw = dbg.addDebuggee(g.f); +var ggw = dbg.addDebuggee(g.f()); +var ghw = dbg.addDebuggee(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 0000000000000000000000000000000000000000..f9f484e9707e6bc5fa0e7e0b10975d102d53fc0e --- /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 0000000000000000000000000000000000000000..3350c8ed294a483ed210db52c42310e04ccdf459 --- /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 0000000000000000000000000000000000000000..93e0aaafe03e2b742e4d1b541ef0a5de4d172d78 --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-12.js @@ -0,0 +1,127 @@ +// 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('new-compartment'); +g1.toSource = function () "[global g1]"; +g1.load(url1); +g1.load(url2); +var g2 = newGlobal('new-compartment'); +g2.toSource = function () "[global g2]"; +g2.load(url1); +g2.load(url2); +var g3 = newGlobal('new-compartment'); + +var dbg = new Debugger(g1, g2, g3); + +function script(func) { + var script = dbg.addDebuggee(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 0000000000000000000000000000000000000000..f99045033fe13f6ec678a038b9d04a09e2339b1a --- /dev/null +++ b/js/src/jit-test/tests/debug/Debugger-findScripts-14.js @@ -0,0 +1,29 @@ +// Debugger.prototype.findScripts can find the innermost script at a given +// source location. +var g = newGlobal('new-compartment'); +var dbg = new Debugger(g); + +function script(f) { + return dbg.addDebuggee(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 0000000000000000000000000000000000000000..00da459fc14c2e98677a9f48316d9cc4d3612014 --- /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/js.msg b/js/src/js.msg index 6690ac8bda11f090275a4cec8806462f0622dfaa..cead265e494f19eb88cf0115b89c27a3018591ce 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -375,3 +375,5 @@ MSG_DEF(JSMSG_CSP_BLOCKED_EVAL, 288, 0, JSEXN_ERR, "call to eval() blocked MSG_DEF(JSMSG_DEBUG_NO_SCOPE_OBJECT, 289, 0, JSEXN_TYPEERR, "declarative Environments don't have binding objects") MSG_DEF(JSMSG_EMPTY_CONSEQUENT, 290, 0, JSEXN_SYNTAXERR, "mistyped ; after conditional?") MSG_DEF(JSMSG_NOT_ITERABLE, 291, 1, JSEXN_TYPEERR, "{0} is not iterable") +MSG_DEF(JSMSG_QUERY_LINE_WITHOUT_URL, 292, 0, JSEXN_TYPEERR, "findScripts query object has 'line' property, but no 'url' property") +MSG_DEF(JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL, 293, 0, JSEXN_TYPEERR, "findScripts query object has 'innermost' property without both 'url' and 'line' properties") diff --git a/js/src/jsatom.tbl b/js/src/jsatom.tbl index 35cddbb076dc49638fe60d87d5fe2800523e0dfd..e2c6f3ba73941c0cf078a5167c1690bb03a2af9d 100644 --- a/js/src/jsatom.tbl +++ b/js/src/jsatom.tbl @@ -121,3 +121,5 @@ DEFINE_PROTOTYPE_ATOM(WeakMap) DEFINE_ATOM(byteLength, "byteLength") DEFINE_KEYWORD_ATOM(return) DEFINE_KEYWORD_ATOM(throw) +DEFINE_ATOM(url, "url") +DEFINE_ATOM(innermost, "innermost") diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 605ab21a66c26a7a737b60d72c8d8dc664b4d1db..2c324ba418ad74cb37105bc442b49cb4181b84be 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -1972,70 +1972,356 @@ Debugger::removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global, debuggees.remove(global); } -/* A set of JSCompartment pointers. */ -typedef HashSet<JSCompartment *, DefaultHasher<JSCompartment *>, RuntimeAllocPolicy> CompartmentSet; +/* + * A class for parsing 'findScripts' query arguments and searching for + * scripts that match the criteria they represent. + */ +class Debugger::ScriptQuery { + public: + /* Construct a ScriptQuery to use matching scripts for |dbg|. */ + ScriptQuery(JSContext *cx, Debugger *dbg): + cx(cx), debugger(dbg), compartments(cx), innermostForGlobal(cx) {} -JSBool -Debugger::findScripts(JSContext *cx, unsigned argc, Value *vp) -{ - THIS_DEBUGGER(cx, argc, vp, "findScripts", args, dbg); + /* + * Initialize this ScriptQuery. Raise an error and return false if we + * haven't enough memory. + */ + bool init() { + if (!globals.init() || + !compartments.init() || + !innermostForGlobal.init()) + { + js_ReportOutOfMemory(cx); + return false; + } - CompartmentSet compartments(cx); - if (!compartments.init()) { - js_ReportOutOfMemory(cx); - return false; + return true; } - /* Assemble the set of debuggee compartments. */ - for (GlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty(); r.popFront()) { - if (!compartments.put(r.front()->compartment())) { - js_ReportOutOfMemory(cx); + /* + * Parse the query object |query|, and prepare to match only the scripts + * it specifies. + */ + bool parseQuery(JSObject *query) { + /* + * Check for a 'global' property, which limits the results to those + * scripts scoped to a particular global object. + */ + Value global; + if (!query->getProperty(cx, cx->runtime->atomState.globalAtom, &global)) return false; + if (global.isUndefined()) { + matchAllDebuggeeGlobals(); + } else { + JSObject *referent = debugger->unwrapDebuggeeArgument(cx, global); + if (!referent) + return false; + GlobalObject *globalObject = &referent->global(); + + /* + * If the given global isn't a debuggee, just leave the set of + * acceptable globals empty; we'll return no scripts. + */ + if (debugger->debuggees.has(globalObject)) { + if (!matchSingleGlobal(globalObject)) + return false; + } } - } + + /* Check for a 'url' property. */ + if (!query->getProperty(cx, cx->runtime->atomState.urlAtom, &url)) + return false; + if (!url.isUndefined() && !url.isString()) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNEXPECTED_TYPE, + "query object's 'url' property", "neither undefined nor a string"); + return false; + } + + /* Check for a 'line' property. */ + Value lineProperty; + if (!query->getProperty(cx, cx->runtime->atomState.lineAtom, &lineProperty)) + return false; + if (lineProperty.isUndefined()) { + hasLine = false; + } else if (lineProperty.isNumber()) { + if (url.isUndefined()) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_QUERY_LINE_WITHOUT_URL); + return false; + } + double doubleLine = lineProperty.toNumber(); + if (doubleLine <= 0 || (unsigned int) doubleLine != doubleLine) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_BAD_LINE); + return false; + } + hasLine = true; + line = doubleLine; + } else { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNEXPECTED_TYPE, + "query object's 'line' property", + "neither undefined nor an integer"); + return false; + } + + /* Check for an 'innermost' property. */ + Value innermostProperty; + if (!query->getProperty(cx, cx->runtime->atomState.innermostAtom, &innermostProperty)) + return false; + innermost = js_ValueToBoolean(innermostProperty); + if (innermost) { + /* Technically, we need only check hasLine, but this is clearer. */ + if (url.isUndefined() || !hasLine) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL); + return false; + } + } + + return true; + } + + /* Set up this ScriptQuery appropriately for a missing query argument. */ + bool omittedQuery() { + url.setUndefined(); + hasLine = false; + innermost = false; + return matchAllDebuggeeGlobals(); + } /* - * Accumulate the scripts in an AutoScriptVector, instead of creating - * the JS array as we go, because we mustn't allocate JS objects or GC - * while we use the CellIter. + * Search all relevant compartments and the stack for scripts matching + * this query, and append the matching scripts to |vector|. */ - AutoScriptVector scripts(cx); + bool findScripts(AutoScriptVector *vector) { + if (!prepareQuery()) + return false; + + /* Search each compartment for debuggee scripts. */ + for (CompartmentSet::Range r = compartments.all(); !r.empty(); r.popFront()) { + for (gc::CellIter i(r.front(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) { + JSScript *script = i.get<JSScript>(); + GlobalObject *global = script->getGlobalObjectOrNull(); + if (global && !consider(script, global, vector)) + return false; + } + } + + /* + * Since eval scripts have no global, we need to find them via the call + * stack, where frame's scope tells us the global in use. + */ + for (FrameRegsIter fri(cx); !fri.done(); ++fri) { + if (fri.fp()->isEvalFrame()) { + JSScript *script = fri.fp()->script(); + + /* + * If eval scripts never have global objects set, then we don't need + * to check the existing script vector for duplicates, since we only + * include scripts with globals above. + */ + JS_ASSERT(!script->getGlobalObjectOrNull()); - /* Search each compartment for debuggee scripts. */ - for (CompartmentSet::Range r = compartments.all(); !r.empty(); r.popFront()) { - for (gc::CellIter i(r.front(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) { - JSScript *script = i.get<JSScript>(); - GlobalObject *global = script->getGlobalObjectOrNull(); - if (global && dbg->debuggees.has(global)) { - if (!scripts.append(script)) { + GlobalObject *global = &fri.fp()->scopeChain().global(); + if (!consider(script, global, vector)) + return false; + } + } + + /* + * For most queries, we just accumulate results in 'vector' as we find + * them. But if this is an 'innermost' query, then we've accumulated the + * results in the 'innermostForGlobal' map. In that case, we now need to + * walk that map and populate 'vector'. + */ + if (innermost) { + for (GlobalToScriptMap::Range r = innermostForGlobal.all(); !r.empty(); r.popFront()) { + if (!vector->append(r.front().value)) { js_ReportOutOfMemory(cx); return false; - } + } } } + + return true; } + private: + /* The context in which we should do our work. */ + JSContext *cx; + + /* The debugger for which we conduct queries. */ + Debugger *debugger; + + /* A script must run in one of these globals to match the query. */ + GlobalObjectSet globals; + + typedef HashSet<JSCompartment *, DefaultHasher<JSCompartment *>, RuntimeAllocPolicy> + CompartmentSet; + + /* The smallest set of compartments that contains all globals in globals. */ + CompartmentSet compartments; + + /* If this is a string, matching scripts have urls equal to it. */ + Value url; + + /* url as a C string. */ + JSAutoByteString urlCString; + + /* True if the query contained a 'line' property. */ + bool hasLine; + + /* The line matching scripts must cover. */ + unsigned int line; + + /* True if the query has an 'innermost' property whose value is true. */ + bool innermost; + + typedef HashMap<GlobalObject *, JSScript *, DefaultHasher<GlobalObject *>, RuntimeAllocPolicy> + GlobalToScriptMap; + /* - * Since eval scripts have no global, we need to find them via the call - * stack, where frame's scope tells us the global in use. + * For 'innermost' queries, a map from global objects to the innermost + * script we've seen so far in that global. (Instantiation code size + * explosion ho!) + */ + GlobalToScriptMap innermostForGlobal; + + /* Arrange for this ScriptQuery to match only scripts that run in |global|. */ + bool matchSingleGlobal(GlobalObject *global) { + JS_ASSERT(globals.count() == 0); + if (!globals.put(global)) { + js_ReportOutOfMemory(cx); + return false; + } + return true; + } + + /* + * Arrange for this ScriptQuery to match all scripts running in debuggee + * globals. + */ + bool matchAllDebuggeeGlobals() { + JS_ASSERT(globals.count() == 0); + /* Copy the debugger's set of debuggee globals to our global set. */ + for (GlobalObjectSet::Range r = debugger->debuggees.all(); !r.empty(); r.popFront()) { + if (!globals.put(r.front())) { + js_ReportOutOfMemory(cx); + return false; + } + } + return true; + } + + /* + * Given that parseQuery or omittedQuery has been called, prepare to + * match scripts. Set urlCString as appropriate. + */ + bool prepareQuery() { + /* + * Compute the proper value for |compartments|, given the present + * value of |globals|. + */ + for (GlobalObjectSet::Range r = globals.all(); !r.empty(); r.popFront()) { + if (!compartments.put(r.front()->compartment())) { + js_ReportOutOfMemory(cx); + return false; + } + } + + /* Compute urlCString, if a url was given. */ + if (url.isString()) { + if (!urlCString.encode(cx, url.toString())) + return false; + } + + return true; + } + + /* + * If |script|, a script in |global|, matches this query, append it to + * |vector| or place it in |innermostForGlobal|, as appropriate. Return true + * if no error occurs, false if an error occurs. */ - for (FrameRegsIter fri(cx); !fri.done(); ++fri) { - if (fri.fp()->isEvalFrame() && dbg->debuggees.has(&fri.fp()->scopeChain().global())) { - JSScript *script = fri.fp()->script(); + bool consider(JSScript *script, GlobalObject *global, AutoScriptVector *vector) { + if (!globals.has(global)) + return true; + if (urlCString.ptr()) { + if (!script->filename || strcmp(script->filename, urlCString.ptr()) != 0) + return true; + } + if (hasLine) { + if (line < script->lineno || script->lineno + js_GetScriptLineExtent(script) < line) + return true; + } + if (innermost) { /* - * If eval scripts never have global objects set, then we don't need - * to check the existing script vector for duplicates, since we only - * include scripts with globals above. + * For 'innermost' queries, we don't place scripts in |vector| right + * away; we may later find another script that is nested inside this + * one. Instead, we record the innermost script we've found so far + * for each global in innermostForGlobal, and only populate |vector| + * at the bottom of findScripts, when we've traversed all the + * scripts. + * + * So: check this script against the innermost one we've found so + * far (if any), as recorded in innermostForGlobal, and replace that + * if it's better. */ - JS_ASSERT(!script->getGlobalObjectOrNull()); - if (!scripts.append(script)) { + GlobalToScriptMap::AddPtr p = innermostForGlobal.lookupForAdd(global); + if (p) { + /* Is our newly found script deeper than the last one we found? */ + JSScript *incumbent = p->value; + if (script->staticLevel > incumbent->staticLevel) + p->value = script; + } else { + /* + * This is the first matching script we've encountered for this + * global, so it is thus the innermost such script. + */ + if (!innermostForGlobal.add(p, global, script)) { + js_ReportOutOfMemory(cx); + return false; + } + } + } else { + /* Record this matching script in the results vector. */ + if (!vector->append(script)) { js_ReportOutOfMemory(cx); return false; } } + + return true; + } +}; + +JSBool +Debugger::findScripts(JSContext *cx, unsigned argc, Value *vp) +{ + THIS_DEBUGGER(cx, argc, vp, "findScripts", args, dbg); + + ScriptQuery query(cx, dbg); + if (!query.init()) + return false; + + if (argc >= 1) { + JSObject *queryObject = NonNullObject(cx, args[0]); + if (!queryObject || !query.parseQuery(queryObject)) + return false; + } else { + if (!query.omittedQuery()) + return false; } + /* + * Accumulate the scripts in an AutoScriptVector, instead of creating + * the JS array as we go, because we mustn't allocate JS objects or GC + * while we use the CellIter. + */ + AutoScriptVector scripts(cx); + + if (!query.findScripts(&scripts)) + return false; + JSObject *result = NewDenseAllocatedArray(cx, scripts.length(), NULL); if (!result) return false; diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h index 5824c06a5c8dad4764954df49dd52576eca35c3c..0a9eea33060a99bd24d3036fb3ecb36477c5103c 100644 --- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -120,6 +120,7 @@ class Debugger { ObjectWeakMap environments; class FrameRange; + class ScriptQuery; bool addDebuggeeGlobal(JSContext *cx, GlobalObject *obj); void removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global,