Bug 733461: Implement the 'query' parameter of Debugger.prototype.findScripts. r=jorendorff
authorJim Blandy <jimb@mozilla.com>
Thu, 05 Apr 2012 17:10:44 -0700
changeset 91143 b0da9a35a841bf4cf770eb2d292128d3abc348a6
parent 91142 153cbd729eba7b3660f0a1d782c3d78c5e236abf
child 91144 1caaf6428d93af8d09447952ed4e1b4e729d8bdf
push id667
push usertim.taubert@gmx.de
push dateTue, 10 Apr 2012 10:56:50 +0000
treeherderfx-team@6fe5b0271cd1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs733461
milestone14.0a1
Bug 733461: Implement the 'query' parameter of Debugger.prototype.findScripts. r=jorendorff
js/src/jit-test/jit_test.py
js/src/jit-test/tests/debug/Debugger-findScripts-07.js
js/src/jit-test/tests/debug/Debugger-findScripts-08-script2
js/src/jit-test/tests/debug/Debugger-findScripts-08.js
js/src/jit-test/tests/debug/Debugger-findScripts-09.js
js/src/jit-test/tests/debug/Debugger-findScripts-10.js
js/src/jit-test/tests/debug/Debugger-findScripts-11-script2
js/src/jit-test/tests/debug/Debugger-findScripts-11.js
js/src/jit-test/tests/debug/Debugger-findScripts-12-script1
js/src/jit-test/tests/debug/Debugger-findScripts-12-script2
js/src/jit-test/tests/debug/Debugger-findScripts-12.js
js/src/jit-test/tests/debug/Debugger-findScripts-14.js
js/src/jit-test/tests/debug/Debugger-findScripts-14.script1
js/src/js.msg
js/src/jsatom.tbl
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
--- a/js/src/jit-test/jit_test.py
+++ b/js/src/jit-test/jit_test.py
@@ -127,17 +127,21 @@ def find_tests(dir, substring = None):
             if substring is None or substring in os.path.relpath(test, dir):
                 ans.append(test)
     return ans
 
 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 +
             [ '-e', expr, '-f', os.path.join(lib_dir, 'prolog.js'), '-f', path ])
 
 def set_limits():
     # resource module not supported on all platforms
     try:
new file mode 100644
--- /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);
new file mode 100644
--- /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() {}');
new file mode 100644
--- /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);
new file mode 100644
--- /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);
new file mode 100644
--- /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);
new file mode 100644
--- /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));
+}
new file mode 100644
--- /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);
new file mode 100644
--- /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;
new file mode 100644
--- /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;
new file mode 100644
--- /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]);
new file mode 100644
--- /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);
new file mode 100644
--- /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;
+        };
+    };
+}
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -370,8 +370,10 @@ MSG_DEF(JSMSG_DEBUG_BAD_LINE,         28
 MSG_DEF(JSMSG_DEBUG_NOT_DEBUGGING,    284, 0, JSEXN_ERR, "can't set breakpoint: script global is not a debuggee")
 MSG_DEF(JSMSG_DEBUG_COMPARTMENT_MISMATCH, 285, 2, JSEXN_TYPEERR, "{0}: descriptor .{1} property is an object in a different compartment than the target object")
 MSG_DEF(JSMSG_DEBUG_NOT_SCRIPT_FRAME, 286, 0, JSEXN_ERR, "stack frame is not running JavaScript code")
 MSG_DEF(JSMSG_CANT_WATCH_PROP,        287, 0, JSEXN_TYPEERR, "properties whose names are objects can't be watched")
 MSG_DEF(JSMSG_CSP_BLOCKED_EVAL,       288, 0, JSEXN_ERR, "call to eval() blocked by CSP")
 MSG_DEF(JSMSG_DEBUG_NO_SCOPE_OBJECT,  289, 0, JSEXN_TYPEERR, "declarative Environments don't have binding objects")
 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")
--- a/js/src/jsatom.tbl
+++ b/js/src/jsatom.tbl
@@ -116,8 +116,10 @@ DEFINE_ATOM(fix, "fix")
 DEFINE_ATOM(has, "has")
 DEFINE_ATOM(hasOwn, "hasOwn")
 DEFINE_ATOM(keys, "keys")
 DEFINE_ATOM(iterate, "iterate")
 DEFINE_PROTOTYPE_ATOM(WeakMap)
 DEFINE_ATOM(byteLength, "byteLength")
 DEFINE_KEYWORD_ATOM(return)
 DEFINE_KEYWORD_ATOM(throw)
+DEFINE_ATOM(url, "url")
+DEFINE_ATOM(innermost, "innermost")
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -1967,79 +1967,365 @@ Debugger::removeDebuggeeGlobal(FreeOp *f
     if (v->empty())
         global->compartment()->removeDebuggee(fop, global, compartmentEnum);
     if (debugEnum)
         debugEnum->removeFront();
     else
         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) {}
+
+    /* 
+     * 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;
+        }
+
+        return true;
+    }
+
+    /*
+     * 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();
+    }
+
+    /*
+     * Search all relevant compartments and the stack for scripts matching 
+     * this query, and append the matching scripts to |vector|.
+     */
+    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());
+
+                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;
+
+    /*
+     * 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.
+     */
+    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) {
+            /*
+             * 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.
+             */
+            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);
 
-    CompartmentSet compartments(cx);
-    if (!compartments.init()) {
-        js_ReportOutOfMemory(cx);
+    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;
     }
 
-    /* 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);
-            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);
 
-    /* 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)) {
-                    js_ReportOutOfMemory(cx);
-                    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() && dbg->debuggees.has(&fri.fp()->scopeChain().global())) {
-            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());
-            if (!scripts.append(script)) {
-                js_ReportOutOfMemory(cx);
-                return false;
-            }
-        }
-    }
+    if (!query.findScripts(&scripts))
+        return false;
 
     JSObject *result = NewDenseAllocatedArray(cx, scripts.length(), NULL);
     if (!result)
         return false;
 
     result->ensureDenseArrayInitializedLength(cx, 0, scripts.length());
 
     for (size_t i = 0; i < scripts.length(); i++) {
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -115,16 +115,17 @@ class Debugger {
     /* The map from debuggee objects to their Debugger.Object instances. */
     typedef WeakMap<HeapPtrObject, HeapPtrObject> ObjectWeakMap;
     ObjectWeakMap objects;
 
     /* The map from debuggee Envs to Debugger.Environment instances. */
     ObjectWeakMap environments;
 
     class FrameRange;
+    class ScriptQuery;
 
     bool addDebuggeeGlobal(JSContext *cx, GlobalObject *obj);
     void removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global,
                               GlobalObjectSet::Enum *compartmentEnum,
                               GlobalObjectSet::Enum *debugEnum);
 
     /*
      * Cope with an error or exception in a debugger hook.