Bug 733461: Implement the 'query' parameter of Debugger.prototype.findScripts. r=jorendorff

This commit is contained in:
Jim Blandy 2012-04-05 17:10:44 -07:00
Родитель 9d53cc38ec
Коммит 36c7359221
17 изменённых файлов: 770 добавлений и 47 удалений

Просмотреть файл

@ -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 +

Просмотреть файл

@ -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);

Просмотреть файл

@ -0,0 +1,3 @@
// -*- mode: js2 -*-
g1.eval('function g1g() {}');
g2.eval('function g2g() {}');

Просмотреть файл

@ -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);

Просмотреть файл

@ -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);

Просмотреть файл

@ -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);

Просмотреть файл

@ -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));
}

Просмотреть файл

@ -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);

Просмотреть файл

@ -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;

Просмотреть файл

@ -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;

Просмотреть файл

@ -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]);

Просмотреть файл

@ -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);

Просмотреть файл

@ -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;
};
};
}

Просмотреть файл

@ -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")

Просмотреть файл

@ -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")

Просмотреть файл

@ -1972,27 +1972,345 @@ 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) {}
/*
* 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;
}
/* 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);
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
@ -2001,40 +2319,8 @@ Debugger::findScripts(JSContext *cx, unsigned argc, Value *vp)
*/
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)

Просмотреть файл

@ -120,6 +120,7 @@ class Debugger {
ObjectWeakMap environments;
class FrameRange;
class ScriptQuery;
bool addDebuggeeGlobal(JSContext *cx, GlobalObject *obj);
void removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global,