Bug 637572 - Implement Debugger.Script.prototype.source; r=jimb

This commit is contained in:
Eddy Bruel 2013-05-22 16:42:52 -07:00
parent 5717d5d389
commit 32dbbdb389
6 changed files with 218 additions and 5 deletions

View File

@ -0,0 +1,26 @@
/*
* Script.prototype.source should be an object. Moreover, it should be the
* same object for each child script within the same debugger.
*/
let g = newGlobal('new-compartment');
let dbg = new Debugger(g);
let count = 0;
dbg.onNewScript = function (script) {
assertEq(typeof script.source, "object");
function traverse(script) {
++count;
script.getChildScripts().forEach(function (child) {
assertEq(child.source, script.source);
traverse(child);
});
}
traverse(script);
}
g.eval("2 * 3");
g.eval("function f() {}");
g.eval("function f() { function g() {} }");
g.eval("eval('2 * 3')");
g.eval("new Function('2 * 3')");
assertEq(count, 10);

View File

@ -0,0 +1,16 @@
/*
* Script.prototype.source should be the same object for both the top-level
* script and the script of functions accessed as debuggee values on the global
*/
let g = newGlobal('new-compartment');
let dbg = new Debugger();
let gw = dbg.addDebuggee(g);
let count = 0;
dbg.onDebuggerStatement = function (frame) {
++count;
assertEq(frame.script.source, gw.makeDebuggeeValue(g.f).script.source);
}
g.eval("function f() {}; debugger;");
assertEq(count, 1);

View File

@ -0,0 +1,22 @@
/*
* Script.prototype.source should be a different object for the same script
* within different debuggers.
*/
let g = newGlobal('new-compartment');
let dbg1 = new Debugger(g);
let dbg2 = new Debugger(g);
var count = 0;
var source;
function test(script) {
++count;
if (!source)
source = script.source;
else
assertEq(script.source != source, true);
};
dbg1.onNewScript = test;
dbg2.onNewScript = test;
g.eval("2 * 3");
assertEq(count, 2);

View File

@ -65,6 +65,7 @@ struct CrossCompartmentKey
ObjectWrapper,
StringWrapper,
DebuggerScript,
DebuggerSource,
DebuggerObject,
DebuggerEnvironment
};

View File

@ -74,6 +74,13 @@ enum {
JSSLOT_DEBUGSCRIPT_COUNT
};
extern Class DebuggerSource_class;
enum {
JSSLOT_DEBUGSOURCE_OWNER,
JSSLOT_DEBUGSOURCE_COUNT
};
/*** Utils ***************************************************************************************/
@ -360,7 +367,7 @@ Breakpoint::nextInSite()
Debugger::Debugger(JSContext *cx, JSObject *dbg)
: object(dbg), uncaughtExceptionHook(NULL), enabled(true),
frames(cx), scripts(cx), objects(cx), environments(cx)
frames(cx), scripts(cx), sources(cx), objects(cx), environments(cx)
{
assertSameCompartment(cx, dbg);
@ -389,6 +396,7 @@ Debugger::init(JSContext *cx)
bool ok = debuggees.init() &&
frames.init() &&
scripts.init() &&
sources.init() &&
objects.init() &&
environments.init();
if (!ok)
@ -397,6 +405,7 @@ Debugger::init(JSContext *cx)
}
JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGSCRIPT_OWNER));
JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGSOURCE_OWNER));
JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGOBJECT_OWNER));
JS_STATIC_ASSERT(unsigned(JSSLOT_DEBUGFRAME_OWNER) == unsigned(JSSLOT_DEBUGENV_OWNER));
@ -1380,6 +1389,7 @@ Debugger::markKeysInCompartment(JSTracer *tracer)
objects.markKeys(tracer);
environments.markKeys(tracer);
scripts.markKeys(tracer);
sources.markKeys(tracer);
}
/*
@ -1391,10 +1401,11 @@ Debugger::markKeysInCompartment(JSTracer *tracer)
* cross-compartment WeakMaps in non-GC'd compartments. If their keys and values
* might need to be marked, we have to do it manually.
*
* Each Debugger object keeps three cross-compartment WeakMaps: objects, script,
* and environments. They have the nice property that all their values are in
* the same compartment as the Debugger object, so we only need to mark the
* keys. We must simply mark all keys that are in a compartment being GC'd.
* Each Debugger object keeps foud cross-compartment WeakMaps: objects, scripts,
* script source objects, and environments. They have the nice property that all
* their values are in the same compartment as the Debugger object, so we only
* need to mark the keys. We must simply mark all keys that are in a compartment
* being GC'd.
*
* We must scan all Debugger objects regardless of whether they *currently*
* have any debuggees in a compartment being GC'd, because the WeakMap
@ -1525,6 +1536,7 @@ Debugger::markAll(JSTracer *trc)
MarkObject(trc, &dbgobj, "Debugger Object");
dbg->scripts.trace(trc);
dbg->sources.trace(trc);
dbg->objects.trace(trc);
dbg->environments.trace(trc);
@ -1567,6 +1579,9 @@ Debugger::trace(JSTracer *trc)
/* Trace the weak map from JSScript instances to Debugger.Script objects. */
scripts.trace(trc);
/* Trace the referent ->Debugger.Source weak map */
sources.trace(trc);
/* Trace the referent -> Debugger.Object weak map. */
objects.trace(trc);
@ -1628,6 +1643,7 @@ Debugger::findCompartmentEdges(Zone *zone, js::gc::ComponentFinder<Zone> &finder
if (w == zone || !w->isGCMarking())
continue;
if (dbg->scripts.hasKeyInZone(zone) ||
dbg->sources.hasKeyInZone(zone) ||
dbg->objects.hasKeyInZone(zone) ||
dbg->environments.hasKeyInZone(zone))
{
@ -2817,6 +2833,21 @@ DebuggerScript_getLineCount(JSContext *cx, unsigned argc, Value *vp)
return true;
}
static JSBool
DebuggerScript_getSource(JSContext *cx, unsigned argc, Value *vp)
{
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get source)", args, obj, script);
Debugger *dbg = Debugger::fromChildJSObject(obj);
JS::RootedScriptSource source(cx, script->sourceObject());
RootedObject sourceObject(cx, dbg->wrapSource(cx, source));
if (!sourceObject)
return false;
args.rval().setObject(*sourceObject);
return true;
}
static JSBool
DebuggerScript_getStaticLevel(JSContext *cx, unsigned argc, Value *vp)
{
@ -3453,6 +3484,7 @@ static const JSPropertySpec DebuggerScript_properties[] = {
JS_PSG("url", DebuggerScript_getUrl, 0),
JS_PSG("startLine", DebuggerScript_getStartLine, 0),
JS_PSG("lineCount", DebuggerScript_getLineCount, 0),
JS_PSG("source", DebuggerScript_getSource, 0),
JS_PSG("staticLevel", DebuggerScript_getStaticLevel, 0),
JS_PSG("sourceMapURL", DebuggerScript_getSourceMapUrl, 0),
JS_PS_END
@ -3471,6 +3503,95 @@ static const JSFunctionSpec DebuggerScript_methods[] = {
JS_FS_END
};
/*** Debugger.Source *****************************************************************************/
static void
DebuggerSource_trace(JSTracer *trc, JSObject *obj)
{
/*
* There is a barrier on private pointers, so the Unbarriered marking
* is okay.
*/
if (JSObject *referent = (JSObject *) obj->getPrivate()) {
MarkCrossCompartmentObjectUnbarriered(trc, obj, &referent, "Debugger.Source referent");
obj->setPrivateUnbarriered(referent);
}
}
Class DebuggerSource_class = {
"Source",
JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSOURCE_COUNT),
JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, NULL,
NULL, /* checkAccess */
NULL, /* call */
NULL, /* construct */
NULL, /* hasInstance */
DebuggerSource_trace
};
JSObject *
Debugger::newDebuggerSource(JSContext *cx, JS::HandleScriptSource source)
{
assertSameCompartment(cx, object.get());
JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_SOURCE_PROTO).toObject();
JS_ASSERT(proto);
JSObject *sourceobj = NewObjectWithGivenProto(cx, &DebuggerSource_class, proto, NULL);
if (!sourceobj)
return NULL;
sourceobj->setReservedSlot(JSSLOT_DEBUGSOURCE_OWNER, ObjectValue(*object));
sourceobj->setPrivateGCThing(source);
return sourceobj;
}
JSObject *
Debugger::wrapSource(JSContext *cx, JS::HandleScriptSource source)
{
assertSameCompartment(cx, object.get());
JS_ASSERT(cx->compartment != source->compartment());
SourceWeakMap::AddPtr p = sources.lookupForAdd(source);
if (!p) {
JSObject *sourceobj = newDebuggerSource(cx, source);
if (!sourceobj)
return NULL;
/* The allocation may have caused a GC, which can remove table entries. */
if (!sources.relookupOrAdd(p, source, sourceobj)) {
js_ReportOutOfMemory(cx);
return NULL;
}
CrossCompartmentKey key(CrossCompartmentKey::DebuggerSource, object, source);
if (!object->compartment()->putWrapper(key, ObjectValue(*sourceobj))) {
sources.remove(source);
js_ReportOutOfMemory(cx);
return NULL;
}
}
JS_ASSERT((JSObject *) p->value->getPrivate() == source);
return p->value;
}
static JSBool
DebuggerSource_construct(JSContext *cx, unsigned argc, Value *vp)
{
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_CONSTRUCTOR, "Debugger.Source");
return false;
}
static const JSPropertySpec DebuggerSource_properties[] = {
JS_PS_END
};
static const JSFunctionSpec DebuggerSource_methods[] = {
JS_FS_END
};
/*** Debugger.Frame ******************************************************************************/
@ -5192,6 +5313,7 @@ JS_DefineDebuggerObject(JSContext *cx, JSObject *obj_)
debugProto(cx),
frameProto(cx),
scriptProto(cx),
sourceProto(cx),
objectProto(cx),
envProto(cx);
@ -5221,6 +5343,13 @@ JS_DefineDebuggerObject(JSContext *cx, JSObject *obj_)
if (!scriptProto)
return false;
sourceProto = js_InitClass(cx, debugCtor, sourceProto, &DebuggerSource_class,
DebuggerSource_construct, 0,
DebuggerSource_properties, DebuggerSource_methods,
NULL, NULL);
if (!sourceProto)
return false;
objectProto = js_InitClass(cx, debugCtor, objProto, &DebuggerObject_class,
DebuggerObject_construct, 0,
DebuggerObject_properties, DebuggerObject_methods,
@ -5238,6 +5367,7 @@ JS_DefineDebuggerObject(JSContext *cx, JSObject *obj_)
debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_FRAME_PROTO, ObjectValue(*frameProto));
debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_OBJECT_PROTO, ObjectValue(*objectProto));
debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SCRIPT_PROTO, ObjectValue(*scriptProto));
debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_SOURCE_PROTO, ObjectValue(*sourceProto));
debugProto->setReservedSlot(Debugger::JSSLOT_DEBUG_ENV_PROTO, ObjectValue(*envProto));
return true;
}

View File

@ -172,6 +172,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
JSSLOT_DEBUG_ENV_PROTO,
JSSLOT_DEBUG_OBJECT_PROTO,
JSSLOT_DEBUG_SCRIPT_PROTO,
JSSLOT_DEBUG_SOURCE_PROTO,
JSSLOT_DEBUG_PROTO_STOP,
JSSLOT_DEBUG_HOOK_START = JSSLOT_DEBUG_PROTO_STOP,
JSSLOT_DEBUG_HOOK_STOP = JSSLOT_DEBUG_HOOK_START + HookCount,
@ -216,6 +217,10 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
typedef DebuggerWeakMap<EncapsulatedPtrScript, RelocatablePtrObject> ScriptWeakMap;
ScriptWeakMap scripts;
/* The map from debuggee source script objects to their Debugger.Source instances. */
typedef DebuggerWeakMap<EncapsulatedPtrObject, RelocatablePtrObject> SourceWeakMap;
SourceWeakMap sources;
/* The map from debuggee objects to their Debugger.Object instances. */
typedef DebuggerWeakMap<EncapsulatedPtrObject, RelocatablePtrObject> ObjectWeakMap;
ObjectWeakMap objects;
@ -348,6 +353,12 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
*/
JSObject *newDebuggerScript(JSContext *cx, HandleScript script);
/*
* Allocate and initialize a Debugger.Source instance whose referent is
* |source|.
*/
JSObject *newDebuggerSource(JSContext *cx, JS::HandleScriptSource source);
/*
* Receive a "new script" event from the engine. A new script was compiled
* or deserialized.
@ -505,6 +516,13 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
*/
JSObject *wrapScript(JSContext *cx, HandleScript script);
/*
* Return the Debugger.Source object for |source|, or create a new one if
* needed. The context |cx| must be in the debugger compartment; |source|
* must be a script source object in a debuggee compartment.
*/
JSObject *wrapSource(JSContext *cx, JS::HandleScriptSource source);
private:
Debugger(const Debugger &) MOZ_DELETE;
Debugger & operator=(const Debugger &) MOZ_DELETE;