bug 687966 - eliminating held/unheld scripts in the debugger. r=jorendorff

This commit is contained in:
Igor Bukanov 2011-09-20 21:49:12 +02:00
Родитель 768ce0c081
Коммит c162775c3e
13 изменённых файлов: 117 добавлений и 329 удалений

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

@ -16,19 +16,11 @@ function ApplyToFrameScript(code, skip, f) {
g.eval(code);
}
var savedScript;
ApplyToFrameScript('debugger;', 0,
function (script) {
assertEq(script instanceof Debugger.Script, true);
assertEq(script.live, true);
savedScript = script;
});
assertEq(savedScript.live, false);
ApplyToFrameScript("(function () { eval('debugger;'); })();", 0,
function (script) {
assertEq(script instanceof Debugger.Script, true);
assertEq(script.live, true);
savedScript = script;
});
assertEq(savedScript.live, false);

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

@ -16,15 +16,10 @@ function ApplyToFrameScript(code, skip, f) {
g.eval(code);
}
var savedScript;
ApplyToFrameScript('(function () { debugger; })();', 0,
function (script) {
assertEq(script instanceof Debugger.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,

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

@ -10,5 +10,5 @@ assertEq(arr.length, 10);
gc();
for (var i = 0; i < arr.length; i++)
assertEq(arr[i].live, true); // XXX FIXME - replace with something that touches the script
assertEq(arr[i].lineCount, 1);

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

@ -10,6 +10,6 @@ assertEq(arr.length, 100);
gc(g);
for (var i = 0; i < arr.length; i++)
assertEq(arr[i].live, true); // XXX FIXME replace with something that touches the script
assertEq(arr[i].lineCount, 1);
gc();

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

@ -10,7 +10,6 @@ g.eval("function f() { return 2; }");
var s;
dbg.onDebuggerStatement = function (frame) { s = frame.eval("f").return.script; };
g.eval("debugger;");
assertEq(s.live, true);
s.setBreakpoint(0, {}); // ok
dbg.removeDebuggee(gobj);

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

@ -924,11 +924,8 @@ JSCompartment::markTrapClosuresIteratively(JSTracer *trc)
for (BreakpointSiteMap::Range r = breakpointSites.all(); !r.empty(); r.popFront()) {
BreakpointSite *site = r.front().value;
// Mark jsdbgapi state if any. But if we know the scriptObject, put off
// marking trap state until we know the scriptObject is live.
if (site->trapHandler &&
(!site->scriptObject || !IsAboutToBeFinalized(cx, site->scriptObject)))
{
// Put off marking trap state until we know the script is live.
if (site->trapHandler && !IsAboutToBeFinalized(cx, site->script)) {
if (site->trapClosure.isMarkable() &&
IsAboutToBeFinalized(cx, site->trapClosure.toGCThing()))
{
@ -945,21 +942,19 @@ JSCompartment::sweepBreakpoints(JSContext *cx)
{
for (BreakpointSiteMap::Enum e(breakpointSites); !e.empty(); e.popFront()) {
BreakpointSite *site = e.front().value;
if (site->scriptObject) {
// clearTrap and nextbp are necessary here to avoid possibly
// reading *site or *bp after destroying it.
bool scriptGone = IsAboutToBeFinalized(cx, site->scriptObject);
bool clearTrap = scriptGone && site->hasTrap();
Breakpoint *nextbp;
for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = nextbp) {
nextbp = bp->nextInSite();
if (scriptGone || IsAboutToBeFinalized(cx, bp->debugger->toJSObject()))
bp->destroy(cx, &e);
}
if (clearTrap)
site->clearTrap(cx, &e);
// clearTrap and nextbp are necessary here to avoid possibly
// reading *site or *bp after destroying it.
bool scriptGone = IsAboutToBeFinalized(cx, site->script);
bool clearTrap = scriptGone && site->hasTrap();
Breakpoint *nextbp;
for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = nextbp) {
nextbp = bp->nextInSite();
if (scriptGone || IsAboutToBeFinalized(cx, bp->debugger->toJSObject()))
bp->destroy(cx, &e);
}
if (clearTrap)
site->clearTrap(cx, &e);
}
}

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

@ -2436,7 +2436,7 @@ js_CloneFunctionObject(JSContext *cx, JSFunction *fun, JSObject *parent,
return NULL;
js_CallNewScriptHook(cx, cfun->script(), cfun);
Debugger::onNewScript(cx, cfun->script(), cfun, Debugger::NewHeldScript);
Debugger::onNewScript(cx, cfun->script(), cfun, NULL);
}
}
return clone;

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

@ -4936,14 +4936,6 @@ IsAboutToBeFinalized(JSContext *cx, TypeObjectKey *key)
return !reinterpret_cast<const gc::Cell *>((jsuword) key & ~1)->isMarked();
}
inline bool
ScriptIsAboutToBeFinalized(JSContext *cx, JSScript *script, JSFunction *fun)
{
return script->isCachedEval ||
(script->u.object && IsAboutToBeFinalized(cx, script->u.object)) ||
(fun && IsAboutToBeFinalized(cx, fun));
}
void
TypeDynamicResult(JSContext *cx, JSScript *script, jsbytecode *pc, Type type)
{

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

@ -1251,11 +1251,11 @@ JSScript::NewScriptFromCG(JSContext *cx, JSCodeGenerator *cg)
/* Tell the debugger about this compiled script. */
js_CallNewScriptHook(cx, script, fun);
if (!cg->parent) {
Debugger::onNewScript(cx, script,
fun ? fun : (script->u.object ? script->u.object : cg->scopeChain()),
(fun || script->u.object)
? Debugger::NewHeldScript
: Debugger::NewNonHeldScript);
JSObject *owner = fun ? fun : script->u.object;
GlobalObject *compileAndGoGlobal = NULL;
if (script->compileAndGo)
compileAndGoGlobal = (owner ? owner : cg->scopeChain())->getGlobal();
Debugger::onNewScript(cx, script, owner, compileAndGoGlobal);
}
return script;
@ -1330,7 +1330,6 @@ js_CallDestroyScriptHook(JSContext *cx, JSScript *script)
if (JSDestroyScriptHook hook = cx->debugHooks->destroyScriptHook)
hook(cx, script, cx->debugHooks->destroyScriptHookData);
script->callDestroyHook = false;
Debugger::onDestroyScript(script);
JS_ClearScriptTraps(cx, script);
}

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

@ -291,14 +291,14 @@ class DefaultMarkPolicy<JSObject *, Value> {
};
template <>
class DefaultMarkPolicy<JSObject *, JSObject *> {
class DefaultMarkPolicy<gc::Cell *, JSObject *> {
protected:
JSTracer *tracer;
public:
DefaultMarkPolicy(JSTracer *t) : tracer(t) { }
bool keyMarked(JSObject *k) { return !IsAboutToBeFinalized(tracer->context, k); }
bool keyMarked(gc::Cell *k) { return !IsAboutToBeFinalized(tracer->context, k); }
bool valueMarked(JSObject *v) { return !IsAboutToBeFinalized(tracer->context, v); }
bool markEntryIfLive(JSObject *k, JSObject *v) {
bool markEntryIfLive(gc::Cell *k, JSObject *v) {
if (keyMarked(k) && !valueMarked(v)) {
js::gc::MarkObject(tracer, *v, "WeakMap entry value");
return true;
@ -317,7 +317,7 @@ class DefaultMarkPolicy<JSObject *, JSObject *> {
// default mark policy. We give it a distinct name anyway, in case this ever
// changes.
//
typedef DefaultMarkPolicy<JSObject *, JSObject *> CrossCompartmentMarkPolicy;
typedef DefaultMarkPolicy<gc::Cell *, JSObject *> CrossCompartmentMarkPolicy;
}

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

@ -718,10 +718,11 @@ JS_XDRScript(JSXDRState *xdr, JSScript **scriptp)
return false;
if (xdr->mode == JSXDR_DECODE) {
JS_ASSERT(!script->compileAndGo);
if (!js_NewScriptObject(xdr->cx, script))
return false;
js_CallNewScriptHook(xdr->cx, script, NULL);
Debugger::onNewScript(xdr->cx, script, script->u.object, Debugger::NewHeldScript);
Debugger::onNewScript(xdr->cx, script, script->u.object, NULL);
*scriptp = script;
}

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

@ -312,7 +312,7 @@ Breakpoint::nextInSite()
Debugger::Debugger(JSContext *cx, JSObject *dbg)
: object(dbg), uncaughtExceptionHook(NULL), enabled(true),
frames(cx), objects(cx), heldScripts(cx), nonHeldScripts(cx)
frames(cx), objects(cx), scripts(cx)
{
assertSameCompartment(cx, dbg);
@ -334,11 +334,10 @@ Debugger::~Debugger()
bool
Debugger::init(JSContext *cx)
{
bool ok = (frames.init() &&
objects.init() &&
debuggees.init() &&
heldScripts.init() &&
nonHeldScripts.init());
bool ok = frames.init() &&
objects.init() &&
debuggees.init() &&
scripts.init();
if (!ok)
js_ReportOutOfMemory(cx);
return ok;
@ -405,13 +404,7 @@ Debugger::hasAnyLiveHooks(JSContext *cx) const
/* If any breakpoints are in live scripts, return true. */
for (Breakpoint *bp = firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
/*
* If holder is non-null, examine it to see if the script will be
* collected. If holder is null, then bp->site->script is an eval
* script on the stack, so it is definitely live.
*/
JSObject *holder = bp->site->getScriptObject();
if (!holder || !IsAboutToBeFinalized(cx, holder))
if (!IsAboutToBeFinalized(cx, bp->site->script))
return true;
}
@ -494,7 +487,7 @@ Debugger::wrapDebuggeeValue(JSContext *cx, Value *vp)
if (vp->isObject()) {
JSObject *obj = &vp->toObject();
ObjectWeakMap::AddPtr p = objects.lookupForAdd(obj);
CellWeakMap::AddPtr p = objects.lookupForAdd(obj);
if (p) {
vp->setObject(*p->value);
} else {
@ -738,7 +731,7 @@ Debugger::fireEnterFrame(JSContext *cx)
}
void
Debugger::fireNewScript(JSContext *cx, JSScript *script, JSObject *obj, NewScriptKind kind)
Debugger::fireNewScript(JSContext *cx, JSScript *script, JSObject *obj)
{
JSObject *hook = getHook(OnNewScript);
JS_ASSERT(hook);
@ -748,8 +741,7 @@ Debugger::fireNewScript(JSContext *cx, JSScript *script, JSObject *obj, NewScrip
if (!ac.enter())
return;
JSObject *dsobj =
kind == NewHeldScript ? wrapHeldScript(cx, script, obj) : wrapNonHeldScript(cx, script);
JSObject *dsobj = wrapScript(cx, script, obj);
if (!dsobj) {
handleUncaughtException(ac, NULL, false);
return;
@ -822,8 +814,11 @@ AddNewScriptRecipients(GlobalObject::DebuggerVector *src, AutoValueVector *dest)
}
void
Debugger::slowPathOnNewScript(JSContext *cx, JSScript *script, JSObject *obj, NewScriptKind kind)
Debugger::slowPathOnNewScript(JSContext *cx, JSScript *script, JSObject *obj,
GlobalObject *compileAndGoGlobal)
{
JS_ASSERT(script->compileAndGo == !!compileAndGoGlobal);
/*
* Build the list of recipients. For compile-and-go scripts, this is the
* same as the generic Debugger::dispatchHook code, but non-compile-and-go
@ -831,15 +826,12 @@ Debugger::slowPathOnNewScript(JSContext *cx, JSScript *script, JSObject *obj, Ne
* debugger observing any global in the script's compartment.
*/
AutoValueVector triggered(cx);
GlobalObject *global;
if (script->compileAndGo) {
global = obj->getGlobal();
if (GlobalObject::DebuggerVector *debuggers = global->getDebuggers()) {
if (GlobalObject::DebuggerVector *debuggers = compileAndGoGlobal->getDebuggers()) {
if (!AddNewScriptRecipients(debuggers, &triggered))
return;
}
} else {
global = NULL;
GlobalObjectSet &debuggees = script->compartment()->getDebuggees();
for (GlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
if (!AddNewScriptRecipients(r.front()->getDebuggers(), &triggered))
@ -853,8 +845,10 @@ Debugger::slowPathOnNewScript(JSContext *cx, JSScript *script, JSObject *obj, Ne
*/
for (Value *p = triggered.begin(); p != triggered.end(); p++) {
Debugger *dbg = Debugger::fromJSObject(&p->toObject());
if ((!global || dbg->debuggees.has(global)) && dbg->enabled && dbg->getHook(OnNewScript))
dbg->fireNewScript(cx, script, obj, kind);
if ((!compileAndGoGlobal || dbg->debuggees.has(compileAndGoGlobal)) &&
dbg->enabled && dbg->getHook(OnNewScript)) {
dbg->fireNewScript(cx, script, obj);
}
}
}
@ -1011,7 +1005,7 @@ Debugger::onSingleStep(JSContext *cx, Value *vp)
/*** Debugger JSObjects **************************************************************************/
void
Debugger::markKeysInCompartment(JSTracer *tracer, const ObjectWeakMap &map)
Debugger::markKeysInCompartment(JSTracer *tracer, const CellWeakMap &map, bool scripts)
{
JSCompartment *comp = tracer->context->runtime->gcCurrentCompartment;
JS_ASSERT(comp);
@ -1021,12 +1015,19 @@ Debugger::markKeysInCompartment(JSTracer *tracer, const ObjectWeakMap &map)
* enumerating WeakMap keys. However in this case we need access, so we
* make a base-class reference. Range is public in HashMap.
*/
typedef HashMap<JSObject *, JSObject *, DefaultHasher<JSObject *>, RuntimeAllocPolicy> Map;
typedef HashMap<gc::Cell *, JSObject *, DefaultHasher<gc::Cell *>, RuntimeAllocPolicy> Map;
const Map &storage = map;
for (Map::Range r = storage.all(); !r.empty(); r.popFront()) {
JSObject *key = r.front().key;
if (key->compartment() == comp && IsAboutToBeFinalized(tracer->context, key))
js::gc::MarkObject(tracer, *key, "cross-compartment WeakMap key");
gc::Cell *key = r.front().key;
if (key->compartment() == comp && IsAboutToBeFinalized(tracer->context, key)) {
if (scripts) {
js::gc::MarkScript(tracer, static_cast<JSScript *>(key),
"cross-compartment WeakMap key");
} else {
js::gc::MarkObject(tracer, *static_cast<JSObject *>(key),
"cross-compartment WeakMap key");
}
}
}
}
@ -1042,7 +1043,7 @@ Debugger::markKeysInCompartment(JSTracer *tracer, const ObjectWeakMap &map)
* manually.
*
* Each Debugger object keeps two cross-compartment WeakMaps: objects and
* heldScripts. Both have the nice property that all their values are in the
* scripts. Both 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 the compartment being GC'd.
*
@ -1066,8 +1067,8 @@ Debugger::markCrossCompartmentDebuggerObjectReferents(JSTracer *tracer)
for (JSCList *p = &rt->debuggerList; (p = JS_NEXT_LINK(p)) != &rt->debuggerList;) {
Debugger *dbg = Debugger::fromLinks(p);
if (dbg->object->compartment() != comp) {
markKeysInCompartment(tracer, dbg->objects);
markKeysInCompartment(tracer, dbg->heldScripts);
markKeysInCompartment(tracer, dbg->objects, false);
markKeysInCompartment(tracer, dbg->scripts, true);
}
}
}
@ -1145,8 +1146,7 @@ Debugger::markAllIteratively(GCMarker *trc)
if (dbgMarked) {
/* Search for breakpoints to mark. */
for (Breakpoint *bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
JSObject *scriptObject = bp->site->getScriptObject();
if (!scriptObject || !IsAboutToBeFinalized(cx, scriptObject)) {
if (!IsAboutToBeFinalized(cx, bp->site->script)) {
/*
* The debugger and the script are both live.
* Therefore the breakpoint handler is live.
@ -1195,23 +1195,8 @@ Debugger::trace(JSTracer *trc)
/* Trace the referent -> Debugger.Object weak map. */
objects.trace(trc);
/*
* Trace the weak map from JSFunctions and "Script" JSObjects to
* Debugger.Script objects.
*/
heldScripts.trace(trc);
/* Trace the map for non-held scripts, which are explicitly freed. */
for (ScriptMap::Range r = nonHeldScripts.all(); !r.empty(); r.popFront()) {
JSObject *scriptobj = r.front().value;
/*
* nonHeldScripts should only refer to Debugger.Script objects for
* scripts that haven't been freed yet.
*/
JS_ASSERT(scriptobj->getPrivate());
MarkObject(trc, *scriptobj, "live eval Debugger.Script");
}
/* Trace the weak map from JSScript instances to Debugger.Script objects. */
scripts.trace(trc);
}
void
@ -1791,45 +1776,6 @@ JSFunctionSpec Debugger::methods[] = {
/*** Debugger.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.
*
* Debugger.Script instances for held scripts are strong references to the
* holder (and thus to the script). Debugger::heldScripts weakly maps
* debuggee holding objects to the Debugger.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 Debugger.Script is dead anyway, and its
* entry in Debugger::heldScripts will be cleaned up by the standard weak
* table code.
*
* - "Non-held scripts": JSScripts generated temporarily for a call to eval or
* JS_Evaluate*, live until the call completes, at which point the script is
* destroyed.
*
* A Debugger.Script instance for a non-held script has no influence on the
* JSScript's lifetime. Debugger::nonHeldScripts maps live JSScripts to to
* their Debugger.Script objects. When a destroyScript event tells us that
* a non-held script is dead, we remove its table entry, and clear its
* Debugger.Script object's script pointer, thus marking it dead.
*
* A Debugger.Script's private pointer points directly to the JSScript, or is
* NULL if the Debugger.Script is dead. The JSSLOT_DEBUGSCRIPT_HOLDER slot
* refers to the holding object, or is null for non-held JSScripts. The private
* pointer is not traced; the holding object reference, if present, is traced
* via DebuggerScript_trace.
*
* (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.)
*/
static inline JSScript *
GetScriptReferent(JSObject *obj)
{
@ -1837,13 +1783,6 @@ GetScriptReferent(JSObject *obj)
return (JSScript *) obj->getPrivate();
}
static inline void
ClearScriptReferent(JSObject *obj)
{
JS_ASSERT(obj->getClass() == &DebuggerScript_class);
obj->setPrivate(NULL);
}
static inline JSObject *
GetScriptHolder(JSObject *obj)
{
@ -1856,6 +1795,8 @@ static void
DebuggerScript_trace(JSTracer *trc, JSObject *obj)
{
if (!trc->context->runtime->gcCurrentCompartment) {
if (JSScript *script = GetScriptReferent(obj))
MarkScript(trc, script, "Debugger.Script referent");
Value v = obj->getReservedSlot(JSSLOT_DEBUGSCRIPT_HOLDER);
if (!v.isUndefined()) {
if (JSObject *obj = (JSObject *) v.toPrivate())
@ -1895,18 +1836,18 @@ Debugger::newDebuggerScript(JSContext *cx, JSScript *script, JSObject *holder)
}
JSObject *
Debugger::wrapHeldScript(JSContext *cx, JSScript *script, JSObject *obj)
Debugger::wrapScript(JSContext *cx, JSScript *script, JSObject *obj)
{
assertSameCompartment(cx, object);
JS_ASSERT(cx->compartment != script->compartment());
JS_ASSERT(script->compartment() == obj->compartment());
JS_ASSERT_IF(obj, script->compartment() == obj->compartment());
ScriptWeakMap::AddPtr p = heldScripts.lookupForAdd(obj);
CellWeakMap::AddPtr p = scripts.lookupForAdd(script);
if (!p) {
JSObject *scriptobj = newDebuggerScript(cx, script, obj);
/* The allocation may have caused a GC, which can remove table entries. */
if (!scriptobj || !heldScripts.relookupOrAdd(p, obj, scriptobj))
if (!scriptobj || !scripts.relookupOrAdd(p, script, scriptobj))
return NULL;
}
@ -1917,61 +1858,11 @@ Debugger::wrapHeldScript(JSContext *cx, JSScript *script, JSObject *obj)
JSObject *
Debugger::wrapFunctionScript(JSContext *cx, JSFunction *fun)
{
return wrapHeldScript(cx, fun->script(), fun);
}
JSObject *
Debugger::wrapJSAPIScript(JSContext *cx, JSObject *obj)
{
JS_ASSERT(obj->isScript());
return wrapHeldScript(cx, obj->getScript(), obj);
}
JSObject *
Debugger::wrapNonHeldScript(JSContext *cx, JSScript *script)
{
assertSameCompartment(cx, object);
JS_ASSERT(cx->compartment != script->compartment());
ScriptMap::AddPtr p = nonHeldScripts.lookupForAdd(script);
if (!p) {
JSObject *scriptobj = newDebuggerScript(cx, script, NULL);
/* The allocation may have caused a GC, which can remove table entries. */
if (!scriptobj || !nonHeldScripts.relookupOrAdd(p, script, scriptobj))
return NULL;
}
JS_ASSERT(GetScriptReferent(p->value) == script);
return p->value;
}
void
Debugger::slowPathOnDestroyScript(JSScript *script)
{
/* Find all debuggers that might have Debugger.Script referring to this script. */
js::GlobalObjectSet *debuggees = &script->compartment()->getDebuggees();
for (GlobalObjectSet::Range r = debuggees->all(); !r.empty(); r.popFront()) {
GlobalObject::DebuggerVector *debuggers = r.front()->getDebuggers();
for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++)
(*p)->destroyNonHeldScript(script);
}
}
void
Debugger::destroyNonHeldScript(JSScript *script)
{
ScriptMap::Ptr p = nonHeldScripts.lookup(script);
if (p) {
JS_ASSERT(GetScriptReferent(p->value) == script);
ClearScriptReferent(p->value);
nonHeldScripts.remove(p);
}
return wrapScript(cx, fun->script(), fun);
}
static JSObject *
DebuggerScript_check(JSContext *cx, const Value &v, const char *clsname, const char *fnname,
bool checkLive)
DebuggerScript_check(JSContext *cx, const Value &v, const char *clsname, const char *fnname)
{
if (!v.isObject()) {
ReportObjectRequired(cx);
@ -1989,42 +1880,33 @@ DebuggerScript_check(JSContext *cx, const Value &v, const char *clsname, const c
* but whose holding object is undefined.
*/
if (thisobj->getReservedSlot(JSSLOT_DEBUGSCRIPT_HOLDER).isUndefined()) {
JS_ASSERT(!GetScriptReferent(thisobj));
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
clsname, fnname, "prototype object");
return NULL;
}
if (checkLive && !GetScriptReferent(thisobj)) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NOT_LIVE,
clsname, fnname, "script");
return NULL;
}
JS_ASSERT(GetScriptReferent(thisobj));
return thisobj;
}
static JSObject *
DebuggerScript_checkThis(JSContext *cx, const CallArgs &args, const char *fnname, bool checkLive)
DebuggerScript_checkThis(JSContext *cx, const CallArgs &args, const char *fnname)
{
return DebuggerScript_check(cx, args.thisv(), "Debugger.Script", fnname, checkLive);
return DebuggerScript_check(cx, args.thisv(), "Debugger.Script", fnname);
}
#define THIS_DEBUGSCRIPT_SCRIPT_NEEDLIVE(cx, argc, vp, fnname, args, obj, script, checkLive) \
#define THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, fnname, args, obj, script) \
CallArgs args = CallArgsFromVp(argc, vp); \
JSObject *obj = DebuggerScript_checkThis(cx, args, fnname, checkLive); \
JSObject *obj = DebuggerScript_checkThis(cx, args, fnname); \
if (!obj) \
return false; \
JSScript *script = GetScriptReferent(obj)
#define THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, fnname, args, obj, script) \
THIS_DEBUGSCRIPT_SCRIPT_NEEDLIVE(cx, argc, vp, fnname, args, obj, script, false)
#define THIS_DEBUGSCRIPT_LIVE_SCRIPT(cx, argc, vp, fnname, args, obj, script) \
THIS_DEBUGSCRIPT_SCRIPT_NEEDLIVE(cx, argc, vp, fnname, args, obj, script, true)
static JSBool
DebuggerScript_getUrl(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGSCRIPT_LIVE_SCRIPT(cx, argc, vp, "get url", args, obj, script);
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getUrl", args, obj, script);
JSString *str = js_NewStringCopyZ(cx, script->filename);
if (!str)
@ -2036,7 +1918,7 @@ DebuggerScript_getUrl(JSContext *cx, uintN argc, Value *vp)
static JSBool
DebuggerScript_getStartLine(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGSCRIPT_LIVE_SCRIPT(cx, argc, vp, "get startLine", args, obj, script);
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getStartLine", args, obj, script);
args.rval().setNumber(script->lineno);
return true;
}
@ -2044,25 +1926,17 @@ DebuggerScript_getStartLine(JSContext *cx, uintN argc, Value *vp)
static JSBool
DebuggerScript_getLineCount(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGSCRIPT_LIVE_SCRIPT(cx, argc, vp, "get lineCount", args, obj, script);
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getLineCount", args, obj, script);
uintN maxLine = js_GetScriptLineExtent(script);
args.rval().setNumber(jsdouble(maxLine));
return true;
}
static JSBool
DebuggerScript_getLive(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "get live", args, obj, script);
args.rval().setBoolean(!!script);
return true;
}
static JSBool
DebuggerScript_getChildScripts(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGSCRIPT_LIVE_SCRIPT(cx, argc, vp, "get live", args, obj, script);
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getChildScripts", args, obj, script);
Debugger *dbg = Debugger::fromChildJSObject(obj);
JSObject *result = NewDenseEmptyArray(cx);
@ -2111,7 +1985,7 @@ static JSBool
DebuggerScript_getOffsetLine(JSContext *cx, uintN argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Script.getOffsetLine", 1);
THIS_DEBUGSCRIPT_LIVE_SCRIPT(cx, argc, vp, "getOffsetLine", args, obj, script);
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getOffsetLine", args, obj, script);
size_t offset;
if (!ScriptOffset(cx, script, args[0], &offset))
return false;
@ -2271,7 +2145,7 @@ class FlowGraphSummary : public Vector<size_t> {
static JSBool
DebuggerScript_getAllOffsets(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGSCRIPT_LIVE_SCRIPT(cx, argc, vp, "getAllOffsets", args, obj, script);
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getAllOffsets", args, obj, script);
/*
* First pass: determine which offsets in this script are jump targets and
@ -2329,7 +2203,7 @@ DebuggerScript_getAllOffsets(JSContext *cx, uintN argc, Value *vp)
static JSBool
DebuggerScript_getLineOffsets(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGSCRIPT_LIVE_SCRIPT(cx, argc, vp, "getAllOffsets", args, obj, script);
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getLineOffsets", args, obj, script);
REQUIRE_ARGC("Debugger.Script.getLineOffsets", 1);
/* Parse lineno argument. */
@ -2378,7 +2252,7 @@ static JSBool
DebuggerScript_setBreakpoint(JSContext *cx, uintN argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Script.setBreakpoint", 2);
THIS_DEBUGSCRIPT_LIVE_SCRIPT(cx, argc, vp, "setBreakpoint", args, obj, script);
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "setBreakpoint", args, obj, script);
Debugger *dbg = Debugger::fromChildJSObject(obj);
JSObject *holder = GetScriptHolder(obj);
@ -2414,7 +2288,7 @@ DebuggerScript_setBreakpoint(JSContext *cx, uintN argc, Value *vp)
static JSBool
DebuggerScript_getBreakpoints(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGSCRIPT_LIVE_SCRIPT(cx, argc, vp, "getBreakpoints", args, obj, script);
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getBreakpoints", args, obj, script);
Debugger *dbg = Debugger::fromChildJSObject(obj);
jsbytecode *pc;
@ -2451,7 +2325,7 @@ static JSBool
DebuggerScript_clearBreakpoint(JSContext *cx, uintN argc, Value *vp)
{
REQUIRE_ARGC("Debugger.Script.clearBreakpoint", 1);
THIS_DEBUGSCRIPT_LIVE_SCRIPT(cx, argc, vp, "clearBreakpoint", args, obj, script);
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearBreakpoint", args, obj, script);
Debugger *dbg = Debugger::fromChildJSObject(obj);
JSObject *handler = NonNullObject(cx, args[0]);
@ -2466,7 +2340,7 @@ DebuggerScript_clearBreakpoint(JSContext *cx, uintN argc, Value *vp)
static JSBool
DebuggerScript_clearAllBreakpoints(JSContext *cx, uintN argc, Value *vp)
{
THIS_DEBUGSCRIPT_LIVE_SCRIPT(cx, argc, vp, "clearBreakpoint", args, obj, script);
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearAllBreakpoints", args, obj, script);
Debugger *dbg = Debugger::fromChildJSObject(obj);
script->compartment()->clearBreakpointsIn(cx, dbg, script, NULL);
args.rval().setUndefined();
@ -2484,7 +2358,6 @@ static JSPropertySpec DebuggerScript_properties[] = {
JS_PSG("url", DebuggerScript_getUrl, 0),
JS_PSG("startLine", DebuggerScript_getStartLine, 0),
JS_PSG("lineCount", DebuggerScript_getLineCount, 0),
JS_PSG("live", DebuggerScript_getLive, 0),
JS_PS_END
};
@ -2759,17 +2632,12 @@ DebuggerFrame_getScript(JSContext *cx, uintN argc, Value *vp)
}
} else if (fp->isScriptFrame()) {
/*
* eval, JS_Evaluate*, and JS_ExecuteScript all create non-function
* script frames. However, scripts for JS_ExecuteScript are held by
* script objects, and must go in heldScripts, whereas scripts for eval
* and JS_Evaluate* latter are explicitly destroyed when the call
* returns, and must go in nonHeldScripts. Distinguish the two cases by
* checking whether the script has a Script object allocated to it.
* We got eval, JS_Evaluate*, or JS_ExecuteScript non-function script
* frames.
*/
JSScript *script = fp->script();
scriptObject = (script->u.object)
? debug->wrapJSAPIScript(cx, script->u.object)
: debug->wrapNonHeldScript(cx, script);
scriptObject = debug->wrapScript(cx, script,
script->isCachedEval ? NULL : script->u.object);
if (!scriptObject)
return false;
}

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

@ -59,8 +59,6 @@ class Debugger {
friend JSBool (::JS_DefineDebuggerObject)(JSContext *cx, JSObject *obj);
public:
enum NewScriptKind { NewNonHeldScript, NewHeldScript };
enum Hook {
OnDebuggerStatement,
OnExceptionUnwind,
@ -105,41 +103,14 @@ class Debugger {
FrameMap;
FrameMap frames;
typedef WeakMap<gc::Cell *, JSObject *, DefaultHasher<gc::Cell *>, CrossCompartmentMarkPolicy>
CellWeakMap;
/* The map from debuggee objects to their Debugger.Object instances. */
typedef WeakMap<JSObject *, JSObject *, DefaultHasher<JSObject *>, CrossCompartmentMarkPolicy>
ObjectWeakMap;
ObjectWeakMap objects;
CellWeakMap objects;
/*
* An ephemeral map from script-holding objects to Debugger.Script
* instances.
*/
typedef WeakMap<JSObject *, JSObject *, DefaultHasher<JSObject *>, CrossCompartmentMarkPolicy>
ScriptWeakMap;
/*
* Map of Debugger.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 Debugger.Script
* instances, for non-held scripts that are explicitly freed.
*/
typedef HashMap<JSScript *, JSObject *, DefaultHasher<JSScript *>, RuntimeAllocPolicy>
ScriptMap;
/*
* Map from non-held JSScripts to their Debugger.Script objects. Non-held
* scripts are scripts created for eval or JS_Evaluate* calls that are
* explicitly destroyed when the call returns. Debugger.Script objects are
* not strong references to such JSScripts; the Debugger.Script becomes
* "dead" when the eval call returns.
*/
ScriptMap nonHeldScripts;
/* An ephemeral map from JSScript* to Debugger.Script instances. */
CellWeakMap scripts;
bool addDebuggeeGlobal(JSContext *cx, GlobalObject *obj);
void removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
@ -195,7 +166,7 @@ class Debugger {
static void traceObject(JSTracer *trc, JSObject *obj);
void trace(JSTracer *trc);
static void finalize(JSContext *cx, JSObject *obj);
static void markKeysInCompartment(JSTracer *tracer, const ObjectWeakMap &map);
static void markKeysInCompartment(JSTracer *tracer, const CellWeakMap &map, bool scripts);
static Class jsclass;
@ -230,9 +201,7 @@ class Debugger {
static void slowPathOnEnterFrame(JSContext *cx);
static void slowPathOnLeaveFrame(JSContext *cx);
static void slowPathOnNewScript(JSContext *cx, JSScript *script, JSObject *obj,
NewScriptKind kind);
static void slowPathOnDestroyScript(JSScript *script);
GlobalObject *compileAndGoGlobal);
static JSTrapStatus dispatchHook(JSContext *cx, js::Value *vp, Hook which);
JSTrapStatus fireDebuggerStatement(JSContext *cx, Value *vp);
@ -246,21 +215,12 @@ class Debugger {
*/
JSObject *newDebuggerScript(JSContext *cx, JSScript *script, JSObject *obj);
/* Helper function for wrapFunctionScript and wrapJSAPIscript. */
JSObject *wrapHeldScript(JSContext *cx, JSScript *script, JSObject *obj);
/*
* Receive a "new script" event from the engine. A new script was compiled
* or deserialized. If kind is NewHeldScript, obj must be the holder
* object. Otherwise, kind must be NewNonHeldScript, script must be an eval
* or JS_Evaluate* script, and we must have
* obj->getGlobal() == scopeObj->getGlobal()
* where scopeObj is the scope in which the new script will be executed.
* or deserialized. For eval scripts obj must be null, otherwise it must be
* a script object.
*/
void fireNewScript(JSContext *cx, JSScript *script, JSObject *obj, NewScriptKind kind);
/* Remove script from our table of non-held scripts. */
void destroyNonHeldScript(JSScript *script);
void fireNewScript(JSContext *cx, JSScript *script, JSObject *obj);
static inline Debugger *fromLinks(JSCList *links);
inline Breakpoint *firstBreakpoint() const;
@ -302,8 +262,7 @@ class Debugger {
static inline JSTrapStatus onDebuggerStatement(JSContext *cx, js::Value *vp);
static inline JSTrapStatus onExceptionUnwind(JSContext *cx, js::Value *vp);
static inline void onNewScript(JSContext *cx, JSScript *script, JSObject *obj,
NewScriptKind kind);
static inline void onDestroyScript(JSScript *script);
GlobalObject *compileAndGoGlobal);
static JSTrapStatus onTrap(JSContext *cx, Value *vp);
static JSTrapStatus onSingleStep(JSContext *cx, Value *vp);
@ -380,19 +339,12 @@ class Debugger {
JSObject *wrapFunctionScript(JSContext *cx, JSFunction *fun);
/*
* Return the Debugger.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.
* Return the Debugger.Script object for |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. |obj| is either the script holder or
* null for non-held scripts.
*/
JSObject *wrapJSAPIScript(JSContext *cx, JSObject *scriptObj);
/*
* Return the Debugger.Script object for the non-held 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 *wrapNonHeldScript(JSContext *cx, JSScript *script);
JSObject *wrapScript(JSContext *cx, JSScript *script, JSObject *obj);
private:
/* Prohibit copying. */
@ -413,7 +365,7 @@ class BreakpointSite {
private:
/*
* The holder object for script, if known, else NULL. This is NULL for
* non-held scripts and for JSD1 traps. It is always non-null for JSD2
* cached eval scripts and for JSD1 traps. It is always non-null for JSD2
* breakpoints in held scripts.
*/
JSObject *scriptObject;
@ -564,18 +516,13 @@ Debugger::onExceptionUnwind(JSContext *cx, js::Value *vp)
}
void
Debugger::onNewScript(JSContext *cx, JSScript *script, JSObject *obj, NewScriptKind kind)
Debugger::onNewScript(JSContext *cx, JSScript *script, JSObject *obj,
GlobalObject *compileAndGoGlobal)
{
JS_ASSERT_IF(kind == NewHeldScript || script->compileAndGo, obj);
JS_ASSERT_IF(script->compileAndGo, compileAndGoGlobal);
JS_ASSERT_IF(!script->compileAndGo, !compileAndGoGlobal);
if (!script->compartment()->getDebuggees().empty())
slowPathOnNewScript(cx, script, obj, kind);
}
void
Debugger::onDestroyScript(JSScript *script)
{
if (!script->compartment()->getDebuggees().empty())
slowPathOnDestroyScript(script);
slowPathOnNewScript(cx, script, obj, compileAndGoGlobal);
}
extern JSBool