mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Initial support for Debug.Script instances. r=jorendorff, push=jorendorff.
This commit is contained in:
parent
38ba6a702a
commit
d90314e09c
36
js/src/jit-test/tests/debug/Frame-script-01.js
Normal file
36
js/src/jit-test/tests/debug/Frame-script-01.js
Normal file
@ -0,0 +1,36 @@
|
||||
// |jit-test| debug
|
||||
// Frame.prototype.script for eval frames.
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
var dbg = new Debug(g);
|
||||
|
||||
// Apply |f| to each frame that is |skip| frames up from each frame that
|
||||
// executes a 'debugger' statement when evaluating |code| in the global g.
|
||||
function ApplyToFrameScript(code, skip, f) {
|
||||
dbg.hooks = {
|
||||
debuggerHandler: function (frame) {
|
||||
while (skip-- > 0)
|
||||
frame = frame.older;
|
||||
assertEq(frame.type, "eval");
|
||||
f(frame.script);
|
||||
}
|
||||
};
|
||||
g.eval(code);
|
||||
}
|
||||
|
||||
var savedScript;
|
||||
|
||||
ApplyToFrameScript('debugger;', 0,
|
||||
function (script) {
|
||||
assertEq(script instanceof Debug.Script, true);
|
||||
assertEq(script.live, true);
|
||||
savedScript = script;
|
||||
});
|
||||
assertEq(savedScript.live, false);
|
||||
ApplyToFrameScript("(function () { eval('debugger;'); })();", 0,
|
||||
function (script) {
|
||||
assertEq(script instanceof Debug.Script, true);
|
||||
assertEq(script.live, true);
|
||||
savedScript = script;
|
||||
});
|
||||
assertEq(savedScript.live, false);
|
35
js/src/jit-test/tests/debug/Frame-script-02.js
Normal file
35
js/src/jit-test/tests/debug/Frame-script-02.js
Normal file
@ -0,0 +1,35 @@
|
||||
// |jit-test| debug
|
||||
// Frame.prototype.script for call frames.
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
var dbg = new Debug(g);
|
||||
|
||||
// Apply |f| to each frame that is |skip| frames up from each frame that
|
||||
// executes a 'debugger' statement when evaluating |code| in the global g.
|
||||
function ApplyToFrameScript(code, skip, f) {
|
||||
dbg.hooks = {
|
||||
debuggerHandler: function (frame) {
|
||||
while (skip-- > 0)
|
||||
frame = frame.older;
|
||||
assertEq(frame.type, "call");
|
||||
f(frame.script);
|
||||
}
|
||||
};
|
||||
g.eval(code);
|
||||
}
|
||||
|
||||
var savedScript;
|
||||
|
||||
ApplyToFrameScript('(function () { debugger; })();', 0,
|
||||
function (script) {
|
||||
assertEq(script instanceof Debug.Script, true);
|
||||
assertEq(script.live, true);
|
||||
savedScript = script;
|
||||
});
|
||||
assertEq(savedScript.live, true);
|
||||
|
||||
// This would be nice, once we can get host call frames:
|
||||
// ApplyToFrameScript("(function () { debugger; }).call(null);", 1,
|
||||
// function (script) {
|
||||
// assertEq(script, null);
|
||||
// });
|
17
js/src/jit-test/tests/debug/Object-script.js
Normal file
17
js/src/jit-test/tests/debug/Object-script.js
Normal file
@ -0,0 +1,17 @@
|
||||
// |jit-test| debug
|
||||
|
||||
var g = newGlobal('new-compartment');
|
||||
var dbg = new Debug(g);
|
||||
var hits = 0;
|
||||
dbg.hooks = {
|
||||
debuggerHandler: function (frame) {
|
||||
var arr = frame.arguments;
|
||||
assertEq(arr[0].script instanceof Debug.Script, true);
|
||||
assertEq(arr[1].script, undefined);
|
||||
assertEq(arr[2].script, undefined);
|
||||
hits++;
|
||||
}
|
||||
};
|
||||
|
||||
g.eval("(function () { debugger; })(function g(){}, {}, Math.atan2);");
|
||||
assertEq(hits, 1);
|
46
js/src/jit-test/tests/debug/Script-01.js
Normal file
46
js/src/jit-test/tests/debug/Script-01.js
Normal file
@ -0,0 +1,46 @@
|
||||
// |jit-test| debug
|
||||
// We get the same Debug.Script object instance each time we ask.
|
||||
|
||||
var global = newGlobal('new-compartment');
|
||||
global.eval('function f() { debugger; }');
|
||||
global.eval('function g() { debugger; }');
|
||||
|
||||
var debug = new Debug(global);
|
||||
|
||||
function evalAndNoteScripts(prog) {
|
||||
var scripts = {};
|
||||
debug.hooks = {
|
||||
debuggerHandler: function(frame) {
|
||||
if (frame.type == "call")
|
||||
assertEq(frame.script, frame.callee.script);
|
||||
scripts.frame = frame.script;
|
||||
if (frame.arguments[0])
|
||||
scripts.argument = frame.arguments[0].script;
|
||||
}
|
||||
};
|
||||
global.eval(prog);
|
||||
return scripts;
|
||||
}
|
||||
|
||||
// If we create a frame for a function and pass it as a value, those should
|
||||
// both yield the same Debug.Script instance.
|
||||
var scripts = evalAndNoteScripts('f(f)');
|
||||
assertEq(scripts.frame, scripts.argument);
|
||||
var fScript = scripts.argument;
|
||||
|
||||
// If we call a second time, we should still get the same instance.
|
||||
scripts = evalAndNoteScripts('f(f)');
|
||||
assertEq(scripts.frame, fScript);
|
||||
assertEq(scripts.argument, fScript);
|
||||
|
||||
// If we call with a different argument, we should get a different Debug.Script.
|
||||
scripts = evalAndNoteScripts('f(g)');
|
||||
assertEq(scripts.frame !== scripts.argument, true);
|
||||
assertEq(scripts.frame, fScript);
|
||||
var gScript = scripts.argument;
|
||||
|
||||
// See if we can get g via the frame.
|
||||
scripts = evalAndNoteScripts('g(f)');
|
||||
assertEq(scripts.frame !== scripts.argument, true);
|
||||
assertEq(scripts.frame, gScript);
|
||||
assertEq(scripts.argument, fScript);
|
7
js/src/jit-test/tests/debug/Script-02.js
Normal file
7
js/src/jit-test/tests/debug/Script-02.js
Normal file
@ -0,0 +1,7 @@
|
||||
// |jit-test| debug
|
||||
// Debug.Script throws when applied as a constructor.
|
||||
|
||||
load(libdir + 'asserts.js');
|
||||
|
||||
assertThrowsInstanceOf(function() { Debug.Script(); }, TypeError);
|
||||
assertThrowsInstanceOf(function() { new Debug.Script(); }, TypeError);
|
31
js/src/jit-test/tests/debug/Script-gc.js
Normal file
31
js/src/jit-test/tests/debug/Script-gc.js
Normal file
@ -0,0 +1,31 @@
|
||||
// |jit-test| debug
|
||||
// The heldScripts table keeps Debug.Script instances with live referents alive.
|
||||
|
||||
var N = 4;
|
||||
var g = newGlobal('new-compartment');
|
||||
var dbg = new Debug(g);
|
||||
var i;
|
||||
dbg.hooks = {
|
||||
debuggerHandler: function (frame) {
|
||||
assertEq(frame.script instanceof Debug.Script, true);
|
||||
frame.script.id = i;
|
||||
}
|
||||
};
|
||||
|
||||
g.eval('var arr = [];')
|
||||
for (i = 0; i < N; i++) // loop to defeat conservative GC
|
||||
g.eval("arr.push(function () { debugger }); arr[arr.length - 1]();");
|
||||
|
||||
gc();
|
||||
|
||||
var hits;
|
||||
dbg.hooks = {
|
||||
debuggerHandler: function (frame) {
|
||||
hits++;
|
||||
assertEq(frame.script.id, i);
|
||||
}
|
||||
};
|
||||
hits = 0;
|
||||
for (i = 0; i < N; i++)
|
||||
g.arr[i]();
|
||||
assertEq(hits, N);
|
@ -353,7 +353,7 @@ MSG_DEF(JSMSG_MALFORMED_ESCAPE, 270, 1, JSEXN_SYNTAXERR, "malformed {0} ch
|
||||
MSG_DEF(JSMSG_CCW_REQUIRED, 271, 1, JSEXN_TYPEERR, "{0}: argument must be an object from a different compartment")
|
||||
MSG_DEF(JSMSG_DEBUG_BAD_RESUMPTION, 272, 0, JSEXN_TYPEERR, "debugger resumption value must be undefined, {throw: val}, {return: val}, or null")
|
||||
MSG_DEF(JSMSG_ASSIGN_FUNCTION_OR_NULL, 273, 1, JSEXN_TYPEERR, "value assigned to {0} must be a function or null")
|
||||
MSG_DEF(JSMSG_DEBUG_FRAME_NOT_LIVE, 274, 0, JSEXN_ERR, "stack frame is not live")
|
||||
MSG_DEF(JSMSG_DEBUG_NOT_LIVE, 274, 1, JSEXN_ERR, "{0} is not live")
|
||||
MSG_DEF(JSMSG_DEBUG_STREAMS_CROSSED, 275, 0, JSEXN_INTERNALERR, "the debuggee has a cross-compartment wrapper of an object in the debugger compartment; can't create a Debug.Object with that wrapper as its referent")
|
||||
MSG_DEF(JSMSG_DEBUG_OBJECT_WRONG_OWNER, 276, 0, JSEXN_TYPEERR, "Debug.Object belongs to a different Debug")
|
||||
MSG_DEF(JSMSG_DEBUG_OBJECT_PROTO, 277, 0, JSEXN_TYPEERR, "Debug.Object.prototype is not a valid Debug.Object")
|
||||
|
345
js/src/jsdbg.cpp
345
js/src/jsdbg.cpp
@ -78,6 +78,14 @@ enum {
|
||||
JSSLOT_DEBUGOBJECT_COUNT
|
||||
};
|
||||
|
||||
extern Class DebugScript_class;
|
||||
|
||||
enum {
|
||||
JSSLOT_DEBUGSCRIPT_OWNER,
|
||||
JSSLOT_DEBUGSCRIPT_HOLDER, // cross-compartment wrapper
|
||||
JSSLOT_DEBUGSCRIPT_COUNT
|
||||
};
|
||||
|
||||
|
||||
// === Utils
|
||||
|
||||
@ -145,12 +153,14 @@ CheckThisClass(JSContext *cx, Value *vp, Class *clasp, const char *fnname)
|
||||
enum {
|
||||
JSSLOT_DEBUG_FRAME_PROTO,
|
||||
JSSLOT_DEBUG_OBJECT_PROTO,
|
||||
JSSLOT_DEBUG_SCRIPT_PROTO,
|
||||
JSSLOT_DEBUG_COUNT
|
||||
};
|
||||
|
||||
Debug::Debug(JSObject *dbg, JSObject *hooks)
|
||||
: object(dbg), hooksObject(hooks), uncaughtExceptionHook(NULL), enabled(true),
|
||||
hasDebuggerHandler(false), hasThrowHandler(false), objects(dbg->compartment()->rt)
|
||||
hasDebuggerHandler(false), hasThrowHandler(false),
|
||||
objects(dbg->compartment()->rt), heldScripts(dbg->compartment()->rt)
|
||||
{
|
||||
// This always happens within a request on some cx.
|
||||
JSRuntime *rt = dbg->compartment()->rt;
|
||||
@ -170,18 +180,25 @@ Debug::~Debug()
|
||||
bool
|
||||
Debug::init(JSContext *cx)
|
||||
{
|
||||
bool ok = frames.init() && objects.init() && debuggees.init();
|
||||
bool ok = (frames.init() &&
|
||||
objects.init() &&
|
||||
debuggees.init() &&
|
||||
heldScripts.init() &&
|
||||
evalScripts.init());
|
||||
if (!ok)
|
||||
js_ReportOutOfMemory(cx);
|
||||
return ok;
|
||||
}
|
||||
|
||||
JS_STATIC_ASSERT(uintN(JSSLOT_DEBUGFRAME_OWNER) == uintN(JSSLOT_DEBUGOBJECT_OWNER));
|
||||
JS_STATIC_ASSERT(uintN(JSSLOT_DEBUGFRAME_OWNER) == uintN(JSSLOT_DEBUGSCRIPT_OWNER));
|
||||
|
||||
Debug *
|
||||
Debug::fromChildJSObject(JSObject *obj)
|
||||
{
|
||||
JS_ASSERT(obj->clasp == &DebugFrame_class || obj->clasp == &DebugObject_class);
|
||||
JS_ASSERT(obj->clasp == &DebugFrame_class ||
|
||||
obj->clasp == &DebugObject_class ||
|
||||
obj->clasp == &DebugScript_class);
|
||||
JSObject *dbgobj = &obj->getReservedSlot(JSSLOT_DEBUGOBJECT_OWNER).toObject();
|
||||
return fromJSObject(dbgobj);
|
||||
}
|
||||
@ -587,6 +604,20 @@ Debug::trace(JSTracer *trc)
|
||||
|
||||
// Trace the referent -> Debug.Object weak map.
|
||||
objects.trace(trc);
|
||||
|
||||
// Trace the weak map from JSFunctions and "Script" JSObjects to
|
||||
// Debug.Script objects.
|
||||
heldScripts.trace(trc);
|
||||
|
||||
// Trace the map for eval scripts, which are explicitly freed.
|
||||
for (ScriptMap::Range r = evalScripts.all(); !r.empty(); r.popFront()) {
|
||||
JSObject *scriptobj = r.front().value;
|
||||
|
||||
// evalScripts should only refer to Debug.Script objects for
|
||||
// scripts that haven't been freed yet.
|
||||
JS_ASSERT(scriptobj->getPrivate());
|
||||
MarkObject(trc, *scriptobj, "live eval Debug.Script");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -867,7 +898,7 @@ Debug::construct(JSContext *cx, uintN argc, Value *vp)
|
||||
JS_ASSERT(proto->getClass() == &Debug::jsclass);
|
||||
|
||||
// Make the new Debug object. Each one has a reference to
|
||||
// Debug.{Frame,Object}.prototype in reserved slots.
|
||||
// Debug.{Frame,Object,Script}.prototype in reserved slots.
|
||||
JSObject *obj = NewNonFunction<WithProto::Given>(cx, &Debug::jsclass, proto, NULL);
|
||||
if (!obj || !obj->ensureClassReservedSlots(cx))
|
||||
return false;
|
||||
@ -1023,6 +1054,220 @@ JSFunctionSpec Debug::methods[] = {
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
|
||||
// === Debug.Script
|
||||
|
||||
// JSScripts' lifetimes fall into to two categories:
|
||||
//
|
||||
// - "Held scripts": JSScripts belonging to JSFunctions and JSScripts created using JSAPI
|
||||
// have lifetimes determined by the garbage collector. A JSScript itself has no mark bit
|
||||
// of its own. Instead, its holding object manages the JSScript as part of its own
|
||||
// structure: the holder has a mark bit; when the holder is marked it calls
|
||||
// js_TraceScript on its JSScript; and when the holder is freed it explicitly frees its
|
||||
// JSScript.
|
||||
//
|
||||
// Debug.Script instances for held scripts are strong references to the holder (and thus
|
||||
// to the script). Debug::heldScripts weakly maps CCWs for holding objects to the
|
||||
// Debug.Script objects for their JSScripts. We needn't act on a destroyScript event for
|
||||
// a held script: if we get such an event we know its Debug.Script is dead anyway, and
|
||||
// its entry in Debug::heldScripts will be cleaned up by the standard weak table code.
|
||||
//
|
||||
// - "Eval scripts": JSScripts generated temporarily for a call to 'eval' or a related
|
||||
// function live until the call completes, at which point the script is destroyed.
|
||||
//
|
||||
// A Debug.Script instance for an eval script has no influence on the JSScript's
|
||||
// lifetime. Debug::evalScripts maps live JSScripts to to their Debug.Script objects.
|
||||
// When a destroyScript event tells us that an eval script is dead, we remove its table
|
||||
// entry, and clear its Debug.Script object's script pointer, thus marking it dead.
|
||||
//
|
||||
// A Debug.Script's private pointer points directly to the JSScript, or is NULL if the
|
||||
// Debug.Script is dead. The JSSLOT_DEBUGSCRIPT_HOLDER slot refers to a CCW for the
|
||||
// holding object, or is null for eval-like JSScripts. The private pointer is not traced;
|
||||
// the holding object reference is traced, if present.
|
||||
//
|
||||
// (We consider a script saved in and retrieved from the eval cache to have been
|
||||
// destroyed, and then --- mirabile dictu --- re-created at the same address. The
|
||||
// newScriptHook and destroyScriptHook hooks cooperate with this view.)
|
||||
|
||||
Class DebugScript_class = {
|
||||
"Script", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGSCRIPT_COUNT),
|
||||
PropertyStub, PropertyStub, PropertyStub, StrictPropertyStub,
|
||||
EnumerateStub, ResolveStub, ConvertStub
|
||||
};
|
||||
|
||||
static inline JSScript *GetScriptReferent(JSObject *obj) {
|
||||
JS_ASSERT(obj->getClass() == &DebugScript_class);
|
||||
return (JSScript *) obj->getPrivate();
|
||||
}
|
||||
|
||||
static inline void ClearScriptReferent(JSObject *obj) {
|
||||
JS_ASSERT(obj->getClass() == &DebugScript_class);
|
||||
obj->setPrivate(NULL);
|
||||
}
|
||||
|
||||
static inline JSObject *GetScriptHolder(JSObject *obj) {
|
||||
JS_ASSERT(obj->getClass() == &DebugScript_class);
|
||||
return obj->getReservedSlot(JSSLOT_DEBUGSCRIPT_HOLDER).toObjectOrNull();
|
||||
}
|
||||
|
||||
JSObject *
|
||||
Debug::newDebugScript(JSContext *cx, JSScript *script, JSObject *holder)
|
||||
{
|
||||
JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_SCRIPT_PROTO).toObject();
|
||||
JS_ASSERT(proto);
|
||||
JSObject *scriptobj = NewNonFunction<WithProto::Given>(cx, &DebugScript_class, proto, NULL);
|
||||
if (!scriptobj || !scriptobj->ensureClassReservedSlots(cx))
|
||||
return false;
|
||||
scriptobj->setPrivate(script);
|
||||
scriptobj->setReservedSlot(JSSLOT_DEBUGSCRIPT_OWNER, ObjectValue(*object));
|
||||
scriptobj->setReservedSlot(JSSLOT_DEBUGSCRIPT_HOLDER, ObjectOrNullValue(holder));
|
||||
|
||||
return scriptobj;
|
||||
}
|
||||
|
||||
JSObject *
|
||||
Debug::wrapHeldScript(JSContext *cx, JSScript *script, JSObject *obj)
|
||||
{
|
||||
assertSameCompartment(cx, object);
|
||||
|
||||
// Our argument must be in a debuggee compartment; find its CCW, for use as the key in
|
||||
// the table.
|
||||
JS_ASSERT(cx->compartment != obj->compartment());
|
||||
if (!cx->compartment->wrap(cx, &obj))
|
||||
return NULL;
|
||||
|
||||
ScriptWeakMap::AddPtr p = heldScripts.lookupForAdd(obj);
|
||||
if (!p) {
|
||||
JSObject *scriptobj = newDebugScript(cx, script, obj);
|
||||
// The allocation may have caused a GC, which can remove table entries.
|
||||
if (!scriptobj || !heldScripts.relookupOrAdd(p, obj, scriptobj))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JS_ASSERT(GetScriptReferent(p->value) == script);
|
||||
return p->value;
|
||||
}
|
||||
|
||||
JSObject *
|
||||
Debug::wrapFunctionScript(JSContext *cx, JSFunction *fun)
|
||||
{
|
||||
return wrapHeldScript(cx, fun->script(), fun);
|
||||
}
|
||||
|
||||
JSObject *
|
||||
Debug::wrapJSAPIScript(JSContext *cx, JSObject *obj)
|
||||
{
|
||||
JS_ASSERT(obj->isScript());
|
||||
return wrapHeldScript(cx, obj->getScript(), obj);
|
||||
}
|
||||
|
||||
JSObject *
|
||||
Debug::wrapEvalScript(JSContext *cx, JSScript *script)
|
||||
{
|
||||
JS_ASSERT(cx->compartment != script->compartment);
|
||||
ScriptMap::AddPtr p = evalScripts.lookupForAdd(script);
|
||||
if (!p) {
|
||||
JSObject *scriptobj = newDebugScript(cx, script, NULL);
|
||||
// The allocation may have caused a GC, which can remove table entries.
|
||||
if (!scriptobj || !evalScripts.relookupOrAdd(p, script, scriptobj))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JS_ASSERT(GetScriptReferent(p->value) == script);
|
||||
return p->value;
|
||||
}
|
||||
|
||||
void
|
||||
Debug::slowPathOnDestroyScript(JSScript *script)
|
||||
{
|
||||
// Find all debuggers that might have Debug.Script referring to this script.
|
||||
js::GlobalObjectSet *debuggees = &script->compartment->getDebuggees();
|
||||
for (GlobalObjectSet::Range r = debuggees->all(); !r.empty(); r.popFront()) {
|
||||
GlobalObject::DebugVector *debuggers = r.front()->getDebuggers();
|
||||
for (Debug **p = debuggers->begin(); p != debuggers->end(); p++)
|
||||
(*p)->destroyEvalScript(script);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Debug::destroyEvalScript(JSScript *script)
|
||||
{
|
||||
ScriptMap::Ptr p = evalScripts.lookup(script);
|
||||
if (p) {
|
||||
JS_ASSERT(GetScriptReferent(p->value) == script);
|
||||
ClearScriptReferent(p->value);
|
||||
evalScripts.remove(p);
|
||||
}
|
||||
}
|
||||
|
||||
static JSObject *
|
||||
DebugScript_checkThis(JSContext *cx, Value *vp, const char *fnname, bool checkLive)
|
||||
{
|
||||
if (!vp[1].isObject()) {
|
||||
ReportObjectRequired(cx);
|
||||
return NULL;
|
||||
}
|
||||
JSObject *thisobj = &vp[1].toObject();
|
||||
if (thisobj->clasp != &DebugScript_class) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
|
||||
"Debug.Script", fnname, thisobj->getClass()->name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Check for Debug.Script.prototype, which is of class DebugScript_class
|
||||
// but whose holding object is undefined.
|
||||
if (thisobj->getReservedSlot(JSSLOT_DEBUGSCRIPT_HOLDER).isUndefined()) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
|
||||
"Debug.Script", fnname, "prototype object");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (checkLive && !GetScriptReferent(thisobj)) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NOT_LIVE,
|
||||
"Debug.Script", fnname, "script");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return thisobj;
|
||||
}
|
||||
|
||||
#define THIS_DEBUGSCRIPT_SCRIPT_NEEDLIVE(cx, vp, fnname, obj, script, checkLive) \
|
||||
JSObject *obj = DebugScript_checkThis(cx, vp, fnname, checkLive); \
|
||||
if (!obj) \
|
||||
return false; \
|
||||
JSScript *script = GetScriptReferent(obj)
|
||||
|
||||
#define THIS_DEBUGSCRIPT_SCRIPT(cx, vp, fnname, obj, script) \
|
||||
THIS_DEBUGSCRIPT_SCRIPT_NEEDLIVE(cx, vp, fnname, obj, script, false)
|
||||
#define THIS_DEBUGSCRIPT_LIVE_SCRIPT(cx, vp, fnname, obj, script) \
|
||||
THIS_DEBUGSCRIPT_SCRIPT_NEEDLIVE(cx, vp, fnname, obj, script, true)
|
||||
|
||||
|
||||
static JSBool
|
||||
DebugScript_getLive(JSContext *cx, uintN argc, Value *vp)
|
||||
{
|
||||
THIS_DEBUGSCRIPT_SCRIPT(cx, vp, "get live", obj, script);
|
||||
vp->setBoolean(!!script);
|
||||
return true;
|
||||
}
|
||||
|
||||
static JSBool
|
||||
DebugScript_construct(JSContext *cx, uintN argc, Value *vp)
|
||||
{
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_CONSTRUCTOR, "Debug.Script");
|
||||
return false;
|
||||
}
|
||||
|
||||
static JSPropertySpec DebugScript_properties[] = {
|
||||
JS_PSG("live", DebugScript_getLive, 0),
|
||||
JS_PS_END
|
||||
};
|
||||
|
||||
static JSFunctionSpec DebugScript_methods[] = {
|
||||
// JS_FN("getOffsetLine", DebugScript_getOffsetLine, 0, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
|
||||
// === Debug.Frame
|
||||
|
||||
@ -1056,8 +1301,8 @@ CheckThisFrame(JSContext *cx, Value *vp, const char *fnname, bool checkLive)
|
||||
return NULL;
|
||||
}
|
||||
if (checkLive) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_FRAME_NOT_LIVE,
|
||||
"Debug.Frame", fnname);
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NOT_LIVE,
|
||||
"Debug.Frame", fnname, "stack frame");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@ -1243,6 +1488,45 @@ DebugFrame_getArguments(JSContext *cx, uintN argc, Value *vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
static JSBool
|
||||
DebugFrame_getScript(JSContext *cx, uintN argc, Value *vp)
|
||||
{
|
||||
THIS_FRAME(cx, vp, "get script", thisobj, fp);
|
||||
Debug *debug = Debug::fromChildJSObject(thisobj);
|
||||
|
||||
JSObject *scriptObject = NULL;
|
||||
if (fp->isFunctionFrame() && !fp->isEvalFrame()) {
|
||||
JSFunction *callee = fp->callee().getFunctionPrivate();
|
||||
if (callee->isInterpreted()) {
|
||||
scriptObject = debug->wrapFunctionScript(cx, callee);
|
||||
if (!scriptObject)
|
||||
return false;
|
||||
}
|
||||
} else if (fp->isScriptFrame()) {
|
||||
JSScript *script = fp->script();
|
||||
// Both calling a JSAPI script object (via JS_ExecuteScript, say) and
|
||||
// calling 'eval' create non-function script frames. However, scripts
|
||||
// for the former are held by script objects, and must go in
|
||||
// heldScripts, whereas scripts for the latter are explicitly destroyed
|
||||
// when the call returns, and must go in evalScripts. Distinguish the
|
||||
// two cases by checking whether the script has a Script object
|
||||
// allocated to it.
|
||||
if (script->u.object) {
|
||||
JS_ASSERT(!fp->isEvalFrame());
|
||||
scriptObject = debug->wrapJSAPIScript(cx, script->u.object);
|
||||
if (!scriptObject)
|
||||
return false;
|
||||
} else {
|
||||
JS_ASSERT(fp->isEvalFrame());
|
||||
scriptObject = debug->wrapEvalScript(cx, script);
|
||||
if (!scriptObject)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
vp->setObjectOrNull(scriptObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
static JSBool
|
||||
DebugFrame_getLive(JSContext *cx, uintN argc, Value *vp)
|
||||
{
|
||||
@ -1380,14 +1664,15 @@ DebugFrame_construct(JSContext *cx, uintN argc, Value *vp)
|
||||
}
|
||||
|
||||
static JSPropertySpec DebugFrame_properties[] = {
|
||||
JS_PSG("type", DebugFrame_getType, 0),
|
||||
JS_PSG("this", DebugFrame_getThis, 0),
|
||||
JS_PSG("older", DebugFrame_getOlder, 0),
|
||||
JS_PSG("live", DebugFrame_getLive, 0),
|
||||
JS_PSG("callee", DebugFrame_getCallee, 0),
|
||||
JS_PSG("generator", DebugFrame_getGenerator, 0),
|
||||
JS_PSG("constructing", DebugFrame_getConstructing, 0),
|
||||
JS_PSG("arguments", DebugFrame_getArguments, 0),
|
||||
JS_PSG("callee", DebugFrame_getCallee, 0),
|
||||
JS_PSG("constructing", DebugFrame_getConstructing, 0),
|
||||
JS_PSG("generator", DebugFrame_getGenerator, 0),
|
||||
JS_PSG("live", DebugFrame_getLive, 0),
|
||||
JS_PSG("older", DebugFrame_getOlder, 0),
|
||||
JS_PSG("script", DebugFrame_getScript, 0),
|
||||
JS_PSG("this", DebugFrame_getThis, 0),
|
||||
JS_PSG("type", DebugFrame_getType, 0),
|
||||
JS_PS_END
|
||||
};
|
||||
|
||||
@ -1403,7 +1688,7 @@ static JSFunctionSpec DebugFrame_methods[] = {
|
||||
Class DebugObject_class = {
|
||||
"Object", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGOBJECT_COUNT),
|
||||
PropertyStub, PropertyStub, PropertyStub, StrictPropertyStub,
|
||||
EnumerateStub, ResolveStub, ConvertStub, FinalizeStub,
|
||||
EnumerateStub, ResolveStub, ConvertStub
|
||||
};
|
||||
|
||||
static JSObject *
|
||||
@ -1545,6 +1830,28 @@ DebugObject_getParameterNames(JSContext *cx, uintN argc, Value *vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
static JSBool
|
||||
DebugObject_getScript(JSContext *cx, uintN argc, Value *vp)
|
||||
{
|
||||
THIS_DEBUGOBJECT_OWNER_REFERENT(cx, vp, "get script", dbg, obj);
|
||||
|
||||
vp->setUndefined();
|
||||
|
||||
if (!obj->isFunction())
|
||||
return true;
|
||||
|
||||
JSFunction *fun = obj->getFunctionPrivate();
|
||||
if (!fun->isInterpreted())
|
||||
return true;
|
||||
|
||||
JSObject *scriptObject = dbg->wrapFunctionScript(cx, fun);
|
||||
if (!scriptObject)
|
||||
return false;
|
||||
|
||||
vp->setObject(*scriptObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
enum ApplyOrCallMode { ApplyMode, CallMode };
|
||||
|
||||
static JSBool
|
||||
@ -1627,6 +1934,7 @@ static JSPropertySpec DebugObject_properties[] = {
|
||||
JS_PSG("callable", DebugObject_getCallable, 0),
|
||||
JS_PSG("name", DebugObject_getName, 0),
|
||||
JS_PSG("parameterNames", DebugObject_getParameterNames, 0),
|
||||
JS_PSG("script", DebugObject_getScript, 0),
|
||||
JS_PS_END
|
||||
};
|
||||
|
||||
@ -1660,6 +1968,14 @@ JS_DefineDebugObject(JSContext *cx, JSObject *obj)
|
||||
if (!frameProto)
|
||||
return false;
|
||||
|
||||
JSObject *scriptCtor;
|
||||
JSObject *scriptProto = js_InitClass(cx, debugCtor, objProto, &DebugScript_class,
|
||||
DebugScript_construct, 0,
|
||||
DebugScript_properties, DebugScript_methods, NULL, NULL,
|
||||
&scriptCtor);
|
||||
if (!scriptProto || !scriptProto->ensureClassReservedSlots(cx))
|
||||
return false;
|
||||
|
||||
JSObject *objectProto = js_InitClass(cx, debugCtor, objProto, &DebugObject_class,
|
||||
DebugObject_construct, 0,
|
||||
DebugObject_properties, DebugObject_methods, NULL, NULL);
|
||||
@ -1668,5 +1984,6 @@ JS_DefineDebugObject(JSContext *cx, JSObject *obj)
|
||||
|
||||
debugProto->setReservedSlot(JSSLOT_DEBUG_FRAME_PROTO, ObjectValue(*frameProto));
|
||||
debugProto->setReservedSlot(JSSLOT_DEBUG_OBJECT_PROTO, ObjectValue(*objectProto));
|
||||
debugProto->setReservedSlot(JSSLOT_DEBUG_SCRIPT_PROTO, ObjectValue(*scriptProto));
|
||||
return true;
|
||||
}
|
||||
|
@ -76,17 +76,19 @@ class Debug {
|
||||
FrameMap;
|
||||
FrameMap frames;
|
||||
|
||||
// Mark policy for ObjectMap.
|
||||
class ObjectMapMarkPolicy: public DefaultMarkPolicy<JSObject *, JSObject *> {
|
||||
// Mark policy for weak maps where the keys are CCWs, but the liveness of the entry
|
||||
// depends on the CCW's *referent's* markedness, not the CCW itself. This policy is
|
||||
// only usable when marking an entry's value eventually marks the key. These
|
||||
// properties hold for ObjectWeakMap and ScriptWeakMap.
|
||||
class CCWReferentKeyMarkPolicy: public DefaultMarkPolicy<JSObject *, JSObject *> {
|
||||
typedef DefaultMarkPolicy<JSObject *, JSObject *> Base;
|
||||
public:
|
||||
explicit ObjectMapMarkPolicy(JSTracer *tracer) : Base(tracer) { }
|
||||
|
||||
explicit CCWReferentKeyMarkPolicy(JSTracer *tracer) : Base(tracer) { }
|
||||
// The unwrap() call here means that we use the *referent's* mark, not that of the
|
||||
// CCW itself, to decide whether the table entry is live. This seems weird: if the
|
||||
// CCW is not marked, and the referent is, won't we end up keeping the table entry
|
||||
// but GC'ing its key? But it's okay: the Debug.Object always refers to the CCW,
|
||||
// so marking the value marks the CCW.
|
||||
// but GC'ing its key? But it's okay: the value always refers to the CCW, so
|
||||
// marking the value marks the CCW.
|
||||
bool keyMarked(JSObject *k) {
|
||||
JS_ASSERT(k->isCrossCompartmentWrapper());
|
||||
return k->unwrap()->isMarked();
|
||||
@ -102,11 +104,31 @@ class Debug {
|
||||
// debuggee object, you must first find its CCW, and then look that up here.
|
||||
//
|
||||
// Using CCWs for keys when it's really their referents' liveness that determines the
|
||||
// table entry's liveness is delicate; see comments on ObjectMapMarkPolicy.
|
||||
typedef WeakMap<JSObject *, JSObject *, DefaultHasher<JSObject *>, ObjectMapMarkPolicy>
|
||||
// table entry's liveness is delicate; see comments on CCWReferentKeyMarkPolicy.
|
||||
typedef WeakMap<JSObject *, JSObject *, DefaultHasher<JSObject *>, CCWReferentKeyMarkPolicy>
|
||||
ObjectWeakMap;
|
||||
ObjectWeakMap objects;
|
||||
|
||||
// An ephemeral map from JSObject CCWs to Debug.Script instances.
|
||||
typedef WeakMap<JSObject *, JSObject *, DefaultHasher<JSObject *>, CCWReferentKeyMarkPolicy>
|
||||
ScriptWeakMap;
|
||||
|
||||
// Map of Debug.Script instances for garbage-collected JSScripts. For function
|
||||
// scripts, the key is the compiler-created, internal JSFunction; for scripts returned
|
||||
// by JSAPI functions, the key is the "Script"-class JSObject.
|
||||
ScriptWeakMap heldScripts;
|
||||
|
||||
// An ordinary (non-ephemeral) map from JSScripts to Debug.Script instances, for eval
|
||||
// scripts that are explicitly freed.
|
||||
typedef HashMap<JSScript *, JSObject *, DefaultHasher<JSScript *>, SystemAllocPolicy>
|
||||
ScriptMap;
|
||||
|
||||
// Map from eval JSScripts to their Debug.Script objects. "Eval scripts" are scripts
|
||||
// created for 'eval' and similar calls that are explicitly destroyed when the call
|
||||
// returns. Debug.Script objects are not strong references to such JSScripts; the
|
||||
// Debug.Script becomes "dead" when the eval call returns.
|
||||
ScriptMap evalScripts;
|
||||
|
||||
bool addDebuggeeGlobal(JSContext *cx, GlobalObject *obj);
|
||||
void removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
|
||||
GlobalObjectSet::Enum *compartmentEnum,
|
||||
@ -140,6 +162,7 @@ class Debug {
|
||||
inline bool hasAnyLiveHooks() const;
|
||||
|
||||
static void slowPathLeaveStackFrame(JSContext *cx);
|
||||
static void slowPathOnDestroyScript(JSScript *script);
|
||||
|
||||
typedef bool (Debug::*DebugObservesMethod)() const;
|
||||
typedef JSTrapStatus (Debug::*DebugHandleMethod)(JSContext *, Value *) const;
|
||||
@ -153,6 +176,17 @@ class Debug {
|
||||
bool observesThrow() const;
|
||||
JSTrapStatus handleThrow(JSContext *cx, Value *vp);
|
||||
|
||||
// Allocate and initialize a Debug.Script instance whose referent is |script| and
|
||||
// whose holder is |obj|. If |obj| is NULL, this creates a Debug.Script whose holder
|
||||
// is null, for eval scripts.
|
||||
JSObject *newDebugScript(JSContext *cx, JSScript *script, JSObject *obj);
|
||||
|
||||
// Helper function for wrapFunctionScript and wrapJSAPIscript.
|
||||
JSObject *wrapHeldScript(JSContext *cx, JSScript *script, JSObject *obj);
|
||||
|
||||
// Remove script from our table of eval scripts.
|
||||
void destroyEvalScript(JSScript *script);
|
||||
|
||||
public:
|
||||
Debug(JSObject *dbg, JSObject *hooks);
|
||||
~Debug();
|
||||
@ -187,6 +221,7 @@ class Debug {
|
||||
static inline void leaveStackFrame(JSContext *cx);
|
||||
static inline JSTrapStatus onDebuggerStatement(JSContext *cx, js::Value *vp);
|
||||
static inline JSTrapStatus onThrow(JSContext *cx, js::Value *vp);
|
||||
static inline void onDestroyScript(JSScript *script);
|
||||
|
||||
/**************************************** Functions for use by jsdbg.cpp. */
|
||||
|
||||
@ -231,6 +266,22 @@ class Debug {
|
||||
//
|
||||
bool newCompletionValue(AutoCompartment &ac, bool ok, Value val, Value *vp);
|
||||
|
||||
// Return the Debug.Script object for |fun|'s script, or create a new one if needed.
|
||||
// The context |cx| must be in the debugger compartment; |fun| must be a
|
||||
// cross-compartment wrapper referring to the JSFunction in a debuggee compartment.
|
||||
JSObject *wrapFunctionScript(JSContext *cx, JSFunction *fun);
|
||||
|
||||
// Return the Debug.Script object for the Script object |obj|'s JSScript, or create a
|
||||
// new one if needed. The context |cx| must be in the debugger compartment; |obj| must
|
||||
// be a cross-compartment wrapper referring to a script object in a debuggee
|
||||
// compartment.
|
||||
JSObject *wrapJSAPIScript(JSContext *cx, JSObject *scriptObj);
|
||||
|
||||
// Return the Debug.Script object for the eval script |script|, or create a new one if
|
||||
// needed. The context |cx| must be in the debugger compartment; |script| must be a
|
||||
// script in a debuggee compartment.
|
||||
JSObject *wrapEvalScript(JSContext *cx, JSScript *script);
|
||||
|
||||
private:
|
||||
// Prohibit copying.
|
||||
Debug(const Debug &);
|
||||
@ -296,6 +347,13 @@ Debug::onThrow(JSContext *cx, js::Value *vp)
|
||||
DebugHandleMethod(&Debug::handleThrow));
|
||||
}
|
||||
|
||||
void
|
||||
Debug::onDestroyScript(JSScript *script)
|
||||
{
|
||||
if (!script->compartment->getDebuggees().empty())
|
||||
slowPathOnDestroyScript(script);
|
||||
}
|
||||
|
||||
extern JSBool
|
||||
EvaluateInScope(JSContext *cx, JSObject *scobj, StackFrame *fp, const jschar *chars,
|
||||
uintN length, const char *filename, uintN lineno, Value *rval);
|
||||
|
@ -50,6 +50,7 @@
|
||||
#include "jsatom.h"
|
||||
#include "jscntxt.h"
|
||||
#include "jsversion.h"
|
||||
#include "jsdbg.h"
|
||||
#include "jsdbgapi.h"
|
||||
#include "jsemit.h"
|
||||
#include "jsfun.h"
|
||||
@ -1443,6 +1444,7 @@ js_CallDestroyScriptHook(JSContext *cx, JSScript *script)
|
||||
hook = cx->debugHooks->destroyScriptHook;
|
||||
if (hook)
|
||||
hook(cx, script, cx->debugHooks->destroyScriptHookData);
|
||||
Debug::onDestroyScript(script);
|
||||
JS_ClearScriptTraps(cx, script);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user