mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1082761 - Add Debugger.prototype.findObjects; r=jimb
This commit is contained in:
parent
a838546e0a
commit
6a91b2cf5e
@ -95,6 +95,15 @@ struct BreadthFirst {
|
||||
// as many starting points as you like. Return false on OOM.
|
||||
bool addStart(Node node) { return pending.append(node); }
|
||||
|
||||
// Add |node| as a starting point for the traversal (see addStart) and also
|
||||
// add it to the |visited| set. Return false on OOM.
|
||||
bool addStartVisited(Node node) {
|
||||
typename NodeMap::AddPtr ptr = visited.lookupForAdd(node);
|
||||
if (!ptr && !visited.add(ptr, node, typename Handler::NodeData()))
|
||||
return false;
|
||||
return addStart(node);
|
||||
}
|
||||
|
||||
// True if the handler wants us to compute edge names; doing so can be
|
||||
// expensive in time and memory. True by default.
|
||||
bool wantNames;
|
||||
|
@ -378,6 +378,32 @@ other kinds of objects.
|
||||
such scripts appear can be affected by the garbage collector's
|
||||
behavior, so this function's behavior is not entirely deterministic.
|
||||
|
||||
<code>findObjects([<i>query</i>])</code>
|
||||
: Return an array of [`Debugger.Object`][object] instances referring to each
|
||||
live object allocated in the scope of the debuggee globals that matches
|
||||
*query*. Each instance appears only once in the array. *Query* is an object
|
||||
whose properties restrict which objects are returned; an object must meet
|
||||
all the criteria given by *query* to be returned. If *query* is omitted, we
|
||||
return the [`Debugger.Object`][object] instances for all objects allocated
|
||||
in the scope of debuggee globals.
|
||||
|
||||
The *query* object may have the following properties:
|
||||
|
||||
`class`
|
||||
: If present, only return objects whose internal `[[Class]]`'s name
|
||||
matches the given string. Note that in some cases, the prototype object
|
||||
for a given constructor has the same `[[Class]]` as the instances that
|
||||
refer to it, but cannot itself be used as a valid instance of the
|
||||
class. Code gathering objects by class name may need to examine them
|
||||
further before trying to use them.
|
||||
|
||||
All properties of *query* are optional. Passing an empty object returns all
|
||||
objects in debuggee globals.
|
||||
|
||||
Unlike `findScripts`, this function is deterministic and will never return
|
||||
[`Debugger.Object`s][object] referring to previously unreachable objects
|
||||
that had not been collected yet.
|
||||
|
||||
<code>clearBreakpoint(<i>handler</i>)</code>
|
||||
: Remove all breakpoints set in this `Debugger` instance that use
|
||||
<i>handler</i> as their handler. Note that, if breakpoints using other
|
||||
|
4
js/src/jit-test/tests/debug/Debugger-findObjects-01.js
Normal file
4
js/src/jit-test/tests/debug/Debugger-findObjects-01.js
Normal file
@ -0,0 +1,4 @@
|
||||
// In a debugger with no debuggees, findObjects should return no objects.
|
||||
|
||||
var dbg = new Debugger;
|
||||
assertEq(dbg.findObjects().length, 0);
|
18
js/src/jit-test/tests/debug/Debugger-findObjects-02.js
Normal file
18
js/src/jit-test/tests/debug/Debugger-findObjects-02.js
Normal file
@ -0,0 +1,18 @@
|
||||
// In a debuggee with live objects, findObjects finds those objects.
|
||||
|
||||
var g = newGlobal();
|
||||
|
||||
let defObject = v => g.eval(`this.${v} = { toString: () => "[object ${v}]" }`);
|
||||
defObject("a");
|
||||
defObject("b");
|
||||
defObject("c");
|
||||
|
||||
var dbg = new Debugger();
|
||||
var gw = dbg.addDebuggee(g);
|
||||
var aw = gw.makeDebuggeeValue(g.a);
|
||||
var bw = gw.makeDebuggeeValue(g.b);
|
||||
var cw = gw.makeDebuggeeValue(g.c);
|
||||
|
||||
assertEq(dbg.findObjects().indexOf(aw) != -1, true);
|
||||
assertEq(dbg.findObjects().indexOf(bw) != -1, true);
|
||||
assertEq(dbg.findObjects().indexOf(cw) != -1, true);
|
12
js/src/jit-test/tests/debug/Debugger-findObjects-03.js
Normal file
12
js/src/jit-test/tests/debug/Debugger-findObjects-03.js
Normal file
@ -0,0 +1,12 @@
|
||||
// findObjects' result includes objects referenced by other objects.
|
||||
|
||||
var g = newGlobal();
|
||||
var dbg = new Debugger();
|
||||
var gw = dbg.addDebuggee(g);
|
||||
|
||||
g.eval('this.a = { b: {} };');
|
||||
|
||||
var bw = gw.makeDebuggeeValue(g.a.b);
|
||||
|
||||
var objects = dbg.findObjects();
|
||||
assertEq(objects.indexOf(bw) != -1, true);
|
16
js/src/jit-test/tests/debug/Debugger-findObjects-04.js
Normal file
16
js/src/jit-test/tests/debug/Debugger-findObjects-04.js
Normal file
@ -0,0 +1,16 @@
|
||||
// findObjects' result includes objects captured by closures.
|
||||
|
||||
var g = newGlobal();
|
||||
var dbg = new Debugger();
|
||||
var gw = dbg.addDebuggee(g);
|
||||
|
||||
g.eval(`
|
||||
this.f = (function () {
|
||||
let a = { foo: () => {} };
|
||||
return () => a;
|
||||
}());
|
||||
`);
|
||||
|
||||
let objects = dbg.findObjects();
|
||||
let aw = gw.makeDebuggeeValue(g.f());
|
||||
assertEq(objects.indexOf(aw) != -1, true);
|
10
js/src/jit-test/tests/debug/Debugger-findObjects-05.js
Normal file
10
js/src/jit-test/tests/debug/Debugger-findObjects-05.js
Normal file
@ -0,0 +1,10 @@
|
||||
// findObjects' result doesn't include any duplicates.
|
||||
|
||||
var g = newGlobal();
|
||||
var dbg = new Debugger();
|
||||
dbg.addDebuggee(g);
|
||||
|
||||
let objects = dbg.findObjects();
|
||||
let set = new Set(objects);
|
||||
|
||||
assertEq(objects.length, set.size);
|
14
js/src/jit-test/tests/debug/Debugger-findObjects-06.js
Normal file
14
js/src/jit-test/tests/debug/Debugger-findObjects-06.js
Normal file
@ -0,0 +1,14 @@
|
||||
// In a debugger with multiple debuggees, findObjects finds objects from all debuggees.
|
||||
|
||||
var g1 = newGlobal();
|
||||
var g2 = newGlobal();
|
||||
var dbg = new Debugger();
|
||||
var g1w = dbg.addDebuggee(g1);
|
||||
var g2w = dbg.addDebuggee(g2);
|
||||
|
||||
g1.eval('this.a = {};');
|
||||
g2.eval('this.b = {};');
|
||||
|
||||
var objects = dbg.findObjects();
|
||||
assertEq(objects.indexOf(g1w.makeDebuggeeValue(g1.a)) != -1, true);
|
||||
assertEq(objects.indexOf(g2w.makeDebuggeeValue(g2.b)) != -1, true);
|
22
js/src/jit-test/tests/debug/Debugger-findObjects-07.js
Normal file
22
js/src/jit-test/tests/debug/Debugger-findObjects-07.js
Normal file
@ -0,0 +1,22 @@
|
||||
// findObjects can filter objects by class name.
|
||||
|
||||
var g = newGlobal();
|
||||
|
||||
var dbg = new Debugger();
|
||||
var gw = dbg.addDebuggee(g);
|
||||
|
||||
g.eval('this.re = /foo/;');
|
||||
g.eval('this.d = new Date();');
|
||||
|
||||
var rew = gw.makeDebuggeeValue(g.re);
|
||||
var dw = gw.makeDebuggeeValue(g.d);
|
||||
|
||||
var objects;
|
||||
|
||||
objects = dbg.findObjects({ class: "RegExp" });
|
||||
assertEq(objects.indexOf(rew) != -1, true);
|
||||
assertEq(objects.indexOf(dw) == -1, true);
|
||||
|
||||
objects = dbg.findObjects({ class: "Date" });
|
||||
assertEq(objects.indexOf(dw) != -1, true);
|
||||
assertEq(objects.indexOf(rew) == -1, true);
|
12
js/src/jit-test/tests/debug/Debugger-findObjects-08.js
Normal file
12
js/src/jit-test/tests/debug/Debugger-findObjects-08.js
Normal file
@ -0,0 +1,12 @@
|
||||
// Passing bad query properties to Debugger.prototype.findScripts throws.
|
||||
|
||||
load(libdir + 'asserts.js');
|
||||
|
||||
var dbg = new Debugger();
|
||||
var g = newGlobal();
|
||||
|
||||
assertThrowsInstanceOf(() => dbg.findObjects({ class: null }), TypeError);
|
||||
assertThrowsInstanceOf(() => dbg.findObjects({ class: true }), TypeError);
|
||||
assertThrowsInstanceOf(() => dbg.findObjects({ class: 1337 }), TypeError);
|
||||
assertThrowsInstanceOf(() => dbg.findObjects({ class: /re/ }), TypeError);
|
||||
assertThrowsInstanceOf(() => dbg.findObjects({ class: {} }), TypeError);
|
9
js/src/jit-test/tests/debug/Debugger-findObjects-09.js
Normal file
9
js/src/jit-test/tests/debug/Debugger-findObjects-09.js
Normal file
@ -0,0 +1,9 @@
|
||||
// We don't return objects where our query's class name is the prefix of the
|
||||
// object's class name and vice versa.
|
||||
|
||||
var dbg = new Debugger();
|
||||
var g = newGlobal();
|
||||
var gw = dbg.addDebuggee(g);
|
||||
|
||||
assertEq(dbg.findObjects({ class: "Objec" }).length, 0);
|
||||
assertEq(dbg.findObjects({ class: "Objectttttt" }).length, 0);
|
@ -32,6 +32,7 @@
|
||||
macro(caller, caller, "caller") \
|
||||
macro(callFunction, callFunction, "callFunction") \
|
||||
macro(caseFirst, caseFirst, "caseFirst") \
|
||||
macro(class_, class_, "class") \
|
||||
macro(Collator, Collator, "Collator") \
|
||||
macro(CollatorCompareGet, CollatorCompareGet, "Intl_Collator_compare_get") \
|
||||
macro(columnNumber, columnNumber, "columnNumber") \
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "jit/BaselineJIT.h"
|
||||
#include "js/Debug.h"
|
||||
#include "js/GCAPI.h"
|
||||
#include "js/UbiNodeTraverse.h"
|
||||
#include "js/Vector.h"
|
||||
#include "vm/ArgumentsObject.h"
|
||||
#include "vm/DebuggerMemory.h"
|
||||
@ -2982,6 +2983,202 @@ Debugger::findScripts(JSContext *cx, unsigned argc, Value *vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* A class for parsing 'findObjects' query arguments and searching for objects
|
||||
* that match the criteria they represent.
|
||||
*/
|
||||
class MOZ_STACK_CLASS Debugger::ObjectQuery
|
||||
{
|
||||
public:
|
||||
/* Construct an ObjectQuery to use matching scripts for |dbg|. */
|
||||
ObjectQuery(JSContext *cx, Debugger *dbg) :
|
||||
cx(cx), dbg(dbg), className(cx)
|
||||
{}
|
||||
|
||||
/*
|
||||
* Parse the query object |query|, and prepare to match only the objects it
|
||||
* specifies.
|
||||
*/
|
||||
bool parseQuery(HandleObject query) {
|
||||
/* Check for the 'class' property */
|
||||
RootedValue cls(cx);
|
||||
if (!JSObject::getProperty(cx, query, query, cx->names().class_, &cls))
|
||||
return false;
|
||||
if (!cls.isUndefined()) {
|
||||
if (!cls.isString()) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
|
||||
"query object's 'class' property",
|
||||
"neither undefined nor a string");
|
||||
return false;
|
||||
}
|
||||
className = cls;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Set up this ObjectQuery appropriately for a missing query argument. */
|
||||
void omittedQuery() {
|
||||
className.setUndefined();
|
||||
}
|
||||
|
||||
/*
|
||||
* Traverse the heap to find all relevant objects and add them to the
|
||||
* provided vector.
|
||||
*/
|
||||
bool findObjects(AutoObjectVector &objs) {
|
||||
if (!prepareQuery())
|
||||
return false;
|
||||
|
||||
{
|
||||
/*
|
||||
* We can't tolerate the GC moving things around while we're
|
||||
* searching the heap. Check that nothing we do causes a GC.
|
||||
*/
|
||||
JS::AutoCheckCannotGC autoCannotGC;
|
||||
|
||||
Traversal traversal(cx, *this, autoCannotGC);
|
||||
if (!traversal.init())
|
||||
return false;
|
||||
|
||||
/* Add each debuggee global as a start point of our traversal. */
|
||||
for (GlobalObjectSet::Range r = dbg->debuggees.all(); !r.empty(); r.popFront()) {
|
||||
if (!traversal.addStartVisited(JS::ubi::Node(static_cast<JSObject *>(r.front()))))
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterate over all compartments and add traversal start points at
|
||||
* objects that have CCWs in other compartments keeping them alive.
|
||||
*/
|
||||
for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
|
||||
JSCompartment *comp = c.get();
|
||||
if (!comp)
|
||||
continue;
|
||||
for (JSCompartment::WrapperEnum e(comp); !e.empty(); e.popFront()) {
|
||||
const CrossCompartmentKey &key = e.front().key();
|
||||
if (key.kind != CrossCompartmentKey::ObjectWrapper)
|
||||
continue;
|
||||
JSObject *obj = static_cast<JSObject *>(key.wrapped);
|
||||
if (!traversal.addStartVisited(JS::ubi::Node(obj)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!traversal.traverse())
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Iterate over the visited set of nodes and accumulate all
|
||||
* |JSObject|s matching our criteria in the given vector.
|
||||
*/
|
||||
for (Traversal::NodeMap::Range r = traversal.visited.all(); !r.empty(); r.popFront()) {
|
||||
JS::ubi::Node node = r.front().key();
|
||||
if (!node.is<JSObject>())
|
||||
continue;
|
||||
|
||||
JSObject *obj = node.as<JSObject>();
|
||||
|
||||
if (!className.isUndefined()) {
|
||||
const char *objClassName = obj->getClass()->name;
|
||||
if (strcmp(objClassName, classNameCString.ptr()) != 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!objs.append(obj))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* |ubi::Node::BreadthFirst| interface.
|
||||
*
|
||||
* We use an empty traversal function and just iterate over the traversal's
|
||||
* visited set post-facto in |findObjects|.
|
||||
*/
|
||||
|
||||
class NodeData {};
|
||||
typedef JS::ubi::BreadthFirst<ObjectQuery> Traversal;
|
||||
bool operator() (Traversal &, JS::ubi::Node, const JS::ubi::Edge &, NodeData *, bool)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
/* The context in which we should do our work. */
|
||||
JSContext *cx;
|
||||
|
||||
/* The debugger for which we conduct queries. */
|
||||
Debugger *dbg;
|
||||
|
||||
/*
|
||||
* If this is non-null, matching objects will have a class whose name is
|
||||
* this property.
|
||||
*/
|
||||
RootedValue className;
|
||||
|
||||
/* The className member, as a C string. */
|
||||
JSAutoByteString classNameCString;
|
||||
|
||||
/*
|
||||
* Given that either omittedQuery or parseQuery has been called, prepare the
|
||||
* query for matching objects.
|
||||
*/
|
||||
bool prepareQuery() {
|
||||
if (className.isString()) {
|
||||
if (!classNameCString.encodeLatin1(cx, className.toString()))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
bool
|
||||
Debugger::findObjects(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
THIS_DEBUGGER(cx, argc, vp, "findObjects", args, dbg);
|
||||
|
||||
ObjectQuery query(cx, dbg);
|
||||
|
||||
if (args.length() >= 1) {
|
||||
RootedObject queryObject(cx, NonNullObject(cx, args[0]));
|
||||
if (!queryObject || !query.parseQuery(queryObject))
|
||||
return false;
|
||||
} else {
|
||||
query.omittedQuery();
|
||||
}
|
||||
|
||||
/*
|
||||
* Accumulate the objects in an AutoObjectVector, instead of creating the JS
|
||||
* array as we go, because we mustn't allocate JS objects or GC while we
|
||||
* traverse the heap graph.
|
||||
*/
|
||||
AutoObjectVector objects(cx);
|
||||
|
||||
if (!query.findObjects(objects))
|
||||
return false;
|
||||
|
||||
size_t length = objects.length();
|
||||
RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, length));
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
result->ensureDenseInitializedLength(cx, 0, length);
|
||||
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
RootedValue debuggeeVal(cx, ObjectValue(*objects[i]));
|
||||
if (!dbg->wrapDebuggeeValue(cx, &debuggeeVal))
|
||||
return false;
|
||||
result->setDenseElement(i, debuggeeVal);
|
||||
}
|
||||
|
||||
args.rval().setObject(*result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Debugger::findAllGlobals(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
@ -3061,6 +3258,7 @@ const JSFunctionSpec Debugger::methods[] = {
|
||||
JS_FN("getNewestFrame", Debugger::getNewestFrame, 0, 0),
|
||||
JS_FN("clearAllBreakpoints", Debugger::clearAllBreakpoints, 0, 0),
|
||||
JS_FN("findScripts", Debugger::findScripts, 1, 0),
|
||||
JS_FN("findObjects", Debugger::findObjects, 1, 0),
|
||||
JS_FN("findAllGlobals", Debugger::findAllGlobals, 0, 0),
|
||||
JS_FN("makeGlobalObjectReference", Debugger::makeGlobalObjectReference, 1, 0),
|
||||
JS_FS_END
|
||||
|
@ -271,6 +271,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
|
||||
|
||||
class FrameRange;
|
||||
class ScriptQuery;
|
||||
class ObjectQuery;
|
||||
|
||||
bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj);
|
||||
bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj,
|
||||
@ -369,6 +370,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
|
||||
static bool getNewestFrame(JSContext *cx, unsigned argc, Value *vp);
|
||||
static bool clearAllBreakpoints(JSContext *cx, unsigned argc, Value *vp);
|
||||
static bool findScripts(JSContext *cx, unsigned argc, Value *vp);
|
||||
static bool findObjects(JSContext *cx, unsigned argc, Value *vp);
|
||||
static bool findAllGlobals(JSContext *cx, unsigned argc, Value *vp);
|
||||
static bool makeGlobalObjectReference(JSContext *cx, unsigned argc, Value *vp);
|
||||
static bool construct(JSContext *cx, unsigned argc, Value *vp);
|
||||
|
Loading…
Reference in New Issue
Block a user