Debuggees are globals, not compartments.

This commit is contained in:
Jason Orendorff 2011-05-23 11:11:09 -05:00
parent 2e05c87e09
commit 5323e5a91c
12 changed files with 300 additions and 131 deletions

View File

@ -0,0 +1,12 @@
// |jit-test| debug
// Events in a non-debuggee are ignored, even if a debuggee is in the same compartment.
var g1 = newGlobal('new-compartment');
var g2 = g1.eval("newGlobal('same-compartment')");
var dbg = new Debug(g1);
var hits = 0;
dbg.hooks = {debuggerHandler: function () { hits++; }};
g1.eval("debugger;");
assertEq(hits, 1);
g2.eval("debugger;");
assertEq(hits, 1);

View File

@ -2022,7 +2022,7 @@ struct JSClass {
* with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was * with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
* prevously allowed, but is now an ES5 violation and thus unsupported. * prevously allowed, but is now an ES5 violation and thus unsupported.
*/ */
#define JSCLASS_GLOBAL_SLOT_COUNT (JSProto_LIMIT * 3 + 6) #define JSCLASS_GLOBAL_SLOT_COUNT (JSProto_LIMIT * 3 + 7)
#define JSCLASS_GLOBAL_FLAGS \ #define JSCLASS_GLOBAL_FLAGS \
(JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT)) (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT))

View File

@ -147,10 +147,11 @@ JSCompartment::init()
#ifdef JS_METHODJIT #ifdef JS_METHODJIT
if (!(jaegerCompartment = rt->new_<mjit::JaegerCompartment>())) if (!(jaegerCompartment = rt->new_<mjit::JaegerCompartment>()))
return false; return false;
return jaegerCompartment->Initialize(); if (!jaegerCompartment->Initialize())
#else return false;
return true;
#endif #endif
return debuggees.init();
} }
#ifdef JS_METHODJIT #ifdef JS_METHODJIT
@ -602,15 +603,3 @@ JSCompartment::isAboutToBeCollected(JSGCInvocationKind gckind)
{ {
return !hold && (arenaListsAreEmpty() || gckind == GC_LAST_CONTEXT); return !hold && (arenaListsAreEmpty() || gckind == GC_LAST_CONTEXT);
} }
void
JSCompartment::removeDebug(Debug *dbg)
{
for (Debug **p = debuggers.begin(); p != debuggers.end(); p++) {
if (*p == dbg) {
debuggers.erase(p);
return;
}
}
JS_NOT_REACHED("JSCompartment::removeDebug");
}

View File

@ -48,6 +48,7 @@
#include "jsgcstats.h" #include "jsgcstats.h"
#include "jsclist.h" #include "jsclist.h"
#include "jsxml.h" #include "jsxml.h"
#include "vm/GlobalObject.h"
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma warning(push) #pragma warning(push)
@ -381,8 +382,6 @@ class DtoaCache {
} /* namespace js */ } /* namespace js */
struct JS_FRIEND_API(JSCompartment) { struct JS_FRIEND_API(JSCompartment) {
typedef js::Vector<js::Debug *, 0, js::SystemAllocPolicy> DebugVector;
JSRuntime *rt; JSRuntime *rt;
JSPrincipals *principals; JSPrincipals *principals;
js::gc::Chunk *chunk; js::gc::Chunk *chunk;
@ -518,7 +517,11 @@ struct JS_FRIEND_API(JSCompartment) {
BackEdgeMap backEdgeTable; BackEdgeMap backEdgeTable;
DebugVector debuggers; /*
* Weak reference to each global in this compartment that is a debuggee.
* Each global has its own list of debuggers.
*/
js::GlobalObjectSet debuggees;
JSCompartment *thisForCtor() { return this; } JSCompartment *thisForCtor() { return this; }
public: public:
@ -529,13 +532,14 @@ struct JS_FRIEND_API(JSCompartment) {
size_t backEdgeCount(jsbytecode *pc) const; size_t backEdgeCount(jsbytecode *pc) const;
size_t incBackEdgeCount(jsbytecode *pc); size_t incBackEdgeCount(jsbytecode *pc);
const DebugVector &getDebuggers() const { return debuggers; } js::GlobalObjectSet &getDebuggees() { return debuggees; }
bool addDebuggee(js::GlobalObject *global) {
bool addDebug(js::Debug *dbg) {
JS_ASSERT(debugMode); JS_ASSERT(debugMode);
return debuggers.append(dbg); return !!debuggees.put(global);
}
void removeDebuggee(js::GlobalObject *global) {
debuggees.remove(global);
} }
void removeDebug(js::Debug *dbg);
}; };
#define JS_SCRIPTS_TO_GC(cx) ((cx)->compartment->scriptsToGC) #define JS_SCRIPTS_TO_GC(cx) ((cx)->compartment->scriptsToGC)

View File

@ -136,27 +136,37 @@ enum {
JSSLOT_DEBUG_COUNT JSSLOT_DEBUG_COUNT
}; };
Debug::Debug(JSObject *dbg, JSObject *hooks, JSCompartment *compartment) Debug::Debug(JSObject *dbg, JSObject *hooks)
: object(dbg), debuggeeCompartment(compartment), hooksObject(hooks), : object(dbg), debuggeeGlobal(NULL), hooksObject(hooks), uncaughtExceptionHook(NULL),
uncaughtExceptionHook(NULL), enabled(true), hasDebuggerHandler(false), enabled(true), hasDebuggerHandler(false), hasThrowHandler(false)
hasThrowHandler(false)
{ {
// This always happens within a request on some cx. // This always happens within a request on some cx.
AutoLockGC lock(compartment->rt); JSRuntime *rt = dbg->compartment()->rt;
JS_APPEND_LINK(&link, &compartment->rt->debuggerList); AutoLockGC lock(rt);
JS_APPEND_LINK(&link, &rt->debuggerList);
} }
Debug::~Debug() Debug::~Debug()
{ {
// This always happens in the GC thread, so no locking is required.
JS_ASSERT(object->compartment()->rt->gcRunning); JS_ASSERT(object->compartment()->rt->gcRunning);
if (debuggeeGlobal) {
// This happens only during per-compartment GC. See comment in
// Debug::sweepAll.
JS_ASSERT(object->compartment()->rt->gcCurrentCompartment == object->compartment());
removeDebuggee(debuggeeGlobal, NULL);
}
// This always happens in the GC thread, so no locking is required.
JS_REMOVE_LINK(&link); JS_REMOVE_LINK(&link);
} }
bool bool
Debug::init() Debug::init(JSContext *cx)
{ {
return frames.init() && objects.init(); bool ok = frames.init() && objects.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_DEBUGOBJECT_OWNER));
@ -216,9 +226,12 @@ void
Debug::slowPathLeaveStackFrame(JSContext *cx) Debug::slowPathLeaveStackFrame(JSContext *cx)
{ {
StackFrame *fp = cx->fp(); StackFrame *fp = cx->fp();
JSCompartment *compartment = cx->compartment; GlobalObject *global = fp->scopeChain().getGlobal();
const JSCompartment::DebugVector &debuggers = compartment->getDebuggers();
for (Debug **p = debuggers.begin(); p != debuggers.end(); p++) { // FIXME This assumes that only current debuggers of global have Frame
// objects for fp. Adding .removeDebuggee will therefore break this code.
if (GlobalObject::DebugVector *debuggers = global->getDebuggers()) {
for (Debug **p = debuggers->begin(); p != debuggers->end(); p++) {
Debug *dbg = *p; Debug *dbg = *p;
if (FrameMap::Ptr p = dbg->frames.lookup(fp)) { if (FrameMap::Ptr p = dbg->frames.lookup(fp)) {
JSObject *frameobj = p->value; JSObject *frameobj = p->value;
@ -227,6 +240,7 @@ Debug::slowPathLeaveStackFrame(JSContext *cx)
} }
} }
} }
}
bool bool
Debug::wrapDebuggeeValue(JSContext *cx, Value *vp) Debug::wrapDebuggeeValue(JSContext *cx, Value *vp)
@ -469,21 +483,22 @@ Debug::dispatchHook(JSContext *cx, js::Value *vp, DebugObservesMethod observesEv
// Note: In the general case, 'triggered' contains references to objects in // Note: In the general case, 'triggered' contains references to objects in
// different compartments--every compartment *except* this one. // different compartments--every compartment *except* this one.
AutoValueVector triggered(cx); AutoValueVector triggered(cx);
JSCompartment *compartment = cx->compartment; GlobalObject *global = cx->fp()->scopeChain().getGlobal();
const JSCompartment::DebugVector &debuggers = compartment->getDebuggers(); if (GlobalObject::DebugVector *debuggers = global->getDebuggers()) {
for (Debug **p = debuggers.begin(); p != debuggers.end(); p++) { for (Debug **p = debuggers->begin(); p != debuggers->end(); p++) {
Debug *dbg = *p; Debug *dbg = *p;
if ((dbg->*observesEvent)()) { if ((dbg->*observesEvent)()) {
if (!triggered.append(ObjectValue(*dbg->toJSObject()))) if (!triggered.append(ObjectValue(*dbg->toJSObject())))
return JSTRAP_ERROR; return JSTRAP_ERROR;
} }
} }
}
// Deliver the event to each debugger, checking again to make sure it // Deliver the event to each debugger, checking again to make sure it
// should still be delivered. // should still be delivered.
for (Value *p = triggered.begin(); p != triggered.end(); p++) { for (Value *p = triggered.begin(); p != triggered.end(); p++) {
Debug *dbg = Debug::fromJSObject(&p->toObject()); Debug *dbg = Debug::fromJSObject(&p->toObject());
if (dbg->observesCompartment(compartment) && (dbg->*observesEvent)()) { if (dbg->debuggeeGlobal == global && (dbg->*observesEvent)()) {
JSTrapStatus st = (dbg->*handleEvent)(cx, vp); JSTrapStatus st = (dbg->*handleEvent)(cx, vp);
if (st != JSTRAP_CONTINUE) if (st != JSTRAP_CONTINUE)
return st; return st;
@ -497,9 +512,13 @@ Debug::dispatchHook(JSContext *cx, js::Value *vp, DebugObservesMethod observesEv
bool bool
Debug::mark(GCMarker *trc, JSCompartment *comp, JSGCInvocationKind gckind) Debug::mark(GCMarker *trc, JSCompartment *comp, JSGCInvocationKind gckind)
{ {
// Debuggers are marked during the incremental long tail of the GC mark
// phase. This method returns true if it has to mark anything; GC calls it
// repeatedly until it returns false.
bool markedAny = false;
// Search for Debug objects in the given compartment. We do this by // Search for Debug objects in the given compartment. We do this by
// searching all the compartments being debugged. // searching all the compartments being debugged.
bool markedAny = false;
JSRuntime *rt = trc->context->runtime; JSRuntime *rt = trc->context->runtime;
for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); c++) { for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); c++) {
JSCompartment *dc = *c; JSCompartment *dc = *c;
@ -509,8 +528,14 @@ Debug::mark(GCMarker *trc, JSCompartment *comp, JSGCInvocationKind gckind)
// debug itself). If comp is null, this is a global GC and we // debug itself). If comp is null, this is a global GC and we
// search every dc that is live. // search every dc that is live.
if (comp ? dc != comp : !dc->isAboutToBeCollected(gckind)) { if (comp ? dc != comp : !dc->isAboutToBeCollected(gckind)) {
const JSCompartment::DebugVector &debuggers = dc->getDebuggers(); const GlobalObjectSet &debuggees = dc->getDebuggees();
for (Debug **p = debuggers.begin(); p != debuggers.end(); p++) { for (GlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
GlobalObject *global = r.front();
// Every debuggee has at least one debugger, so in this case
// getDebuggers can't return NULL.
const GlobalObject::DebugVector *debuggers = global->getDebuggers();
for (Debug **p = debuggers->begin(); p != debuggers->end(); p++) {
Debug *dbg = *p; Debug *dbg = *p;
JSObject *obj = dbg->toJSObject(); JSObject *obj = dbg->toJSObject();
@ -528,15 +553,15 @@ Debug::mark(GCMarker *trc, JSCompartment *comp, JSGCInvocationKind gckind)
// Handling Debug.Objects: // Handling Debug.Objects:
// //
// If comp is the debuggee's compartment, do nothing. No // If comp is the debuggee's compartment, do nothing. No
// referent objects will be collected, since we have a wrapper // referent objects will be collected, since we have a
// of each one. // wrapper of each one.
// //
// If comp is the debugger's compartment, mark all // If comp is the debugger's compartment, mark all
// Debug.Objects, since the referents might be alive and // Debug.Objects, since the referents might be alive and
// therefore the table entries must remain. // therefore the table entries must remain.
// //
// If comp is null, then for each key (referent-wrapper) that // If comp is null, then for each key (referent-wrapper)
// is marked, mark the corresponding value. // that is marked, mark the corresponding value.
// //
if (!comp || obj->compartment() == comp) { if (!comp || obj->compartment() == comp) {
for (ObjectMap::Range r = dbg->objects.all(); !r.empty(); r.popFront()) { for (ObjectMap::Range r = dbg->objects.all(); !r.empty(); r.popFront()) {
@ -546,7 +571,8 @@ Debug::mark(GCMarker *trc, JSCompartment *comp, JSGCInvocationKind gckind)
if (!r.front().value->isMarked() && if (!r.front().value->isMarked() &&
(comp || r.front().key->unwrap()->isMarked())) (comp || r.front().key->unwrap()->isMarked()))
{ {
MarkObject(trc, *r.front().value, "Debug.Object with live referent"); MarkObject(trc, *r.front().value,
"Debug.Object with live referent");
markedAny = true; markedAny = true;
} }
} }
@ -554,6 +580,7 @@ Debug::mark(GCMarker *trc, JSCompartment *comp, JSGCInvocationKind gckind)
} }
} }
} }
}
return markedAny; return markedAny;
} }
@ -578,34 +605,92 @@ Debug::trace(JSTracer *trc, JSObject *obj)
void void
Debug::sweepAll(JSRuntime *rt) Debug::sweepAll(JSRuntime *rt)
{ {
// Sweep ObjectMap entries for objects being collected.
for (JSCList *p = &rt->debuggerList; (p = JS_NEXT_LINK(p)) != &rt->debuggerList;) { for (JSCList *p = &rt->debuggerList; (p = JS_NEXT_LINK(p)) != &rt->debuggerList;) {
Debug *dbg = (Debug *) ((unsigned char *) p - offsetof(Debug, link)); Debug *dbg = (Debug *) ((unsigned char *) p - offsetof(Debug, link));
// If this Debug is being GC'd, detach it from its debuggees. In the
// case of runtime-wide GC, the debuggee might be GC'd too. Since
// detaching requires access to both objects, this must be done before
// finalize time. However, in a per-compartment GC, it is impossible
// for both objects to be GC'd (since they are in different
// compartments), so in that case we just wait for Debug::finalize.
if (!dbg->object->isMarked()) {
if (dbg->debuggeeGlobal)
dbg->removeDebuggee(dbg->debuggeeGlobal, NULL);
}
// Sweep ObjectMap entries for referents being collected.
for (ObjectMap::Enum e(dbg->objects); !e.empty(); e.popFront()) { for (ObjectMap::Enum e(dbg->objects); !e.empty(); e.popFront()) {
JS_ASSERT(e.front().key->isMarked() == e.front().value->isMarked()); JS_ASSERT(e.front().key->isMarked() == e.front().value->isMarked());
if (!e.front().value->isMarked()) if (!e.front().value->isMarked())
e.removeFront(); e.removeFront();
} }
} }
for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); c++)
sweepCompartment(*c);
}
void
Debug::sweepCompartment(JSCompartment *compartment)
{
// For each debuggee being GC'd, detach it from all its debuggers.
GlobalObjectSet &debuggees = compartment->getDebuggees();
for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) {
GlobalObject *global = e.front();
if (!global->isMarked()) {
const GlobalObject::DebugVector *debuggers = global->getDebuggers();
JS_ASSERT(!debuggers->empty());
for (size_t i = debuggers->length(); i--; )
(*debuggers)[i]->removeDebuggee(global, &e);
}
}
}
void
Debug::detachFromCompartment(JSCompartment *comp)
{
for (GlobalObjectSet::Enum e(comp->getDebuggees()); !e.empty(); e.popFront()) {
GlobalObject *global = e.front();
for (;;) {
GlobalObject::DebugVector *debuggers = global->getDebuggers();
if (!debuggers || debuggers->empty())
break;
debuggers->back()->removeDebuggee(global, &e);
}
e.removeFront();
}
}
void
Debug::removeDebuggee(GlobalObject *global, GlobalObjectSet::Enum *e)
{
JS_ASSERT(global == debuggeeGlobal);
GlobalObject::DebugVector *v = global->getDebuggers();
for (Debug **p = v->begin(); p != v->end(); p++) {
if (*p == this) {
v->erase(p);
if (v->empty()) {
if (e)
e->removeFront();
else
global->compartment()->removeDebuggee(global);
}
debuggeeGlobal = NULL;
return;
}
}
JS_NOT_REACHED("Debug::removeDebugee");
} }
void void
Debug::finalize(JSContext *cx, JSObject *obj) Debug::finalize(JSContext *cx, JSObject *obj)
{ {
Debug *dbg = (Debug *) obj->getPrivate(); Debug *dbg = (Debug *) obj->getPrivate();
if (dbg && dbg->debuggeeCompartment)
dbg->detachFrom(dbg->debuggeeCompartment);
cx->delete_(dbg); cx->delete_(dbg);
} }
void
Debug::detachFrom(JSCompartment *c)
{
JS_ASSERT(c == debuggeeCompartment);
c->removeDebug(this);
debuggeeCompartment = NULL;
}
Class Debug::jsclass = { Class Debug::jsclass = {
"Debug", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUG_COUNT), "Debug", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUG_COUNT),
PropertyStub, PropertyStub, PropertyStub, StrictPropertyStub, PropertyStub, PropertyStub, PropertyStub, StrictPropertyStub,
@ -724,8 +809,8 @@ Debug::construct(JSContext *cx, uintN argc, Value *vp)
} }
// Check that the target compartment is in debug mode. // Check that the target compartment is in debug mode.
JSCompartment *debuggeeCompartment = argobj->getProxyPrivate().toObject().compartment(); GlobalObject *debuggee = argobj->getProxyPrivate().toObject().getGlobal();
if (!debuggeeCompartment->debugMode) { if (!debuggee->compartment()->debugMode) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NEED_DEBUG_MODE); JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NEED_DEBUG_MODE);
return false; return false;
} }
@ -750,15 +835,15 @@ Debug::construct(JSContext *cx, uintN argc, Value *vp)
if (!hooks) if (!hooks)
return false; return false;
Debug *dbg = cx->new_<Debug>(obj, hooks, debuggeeCompartment); Debug *dbg = cx->new_<Debug>(obj, hooks);
if (!dbg) if (!dbg)
return false; return false;
obj->setPrivate(dbg); obj->setPrivate(dbg);
if (!dbg->init() || !debuggeeCompartment->addDebug(dbg)) { if (!dbg->init(cx) || !debuggee->addDebug(cx, dbg)) {
js_ReportOutOfMemory(cx); cx->delete_(dbg);
return false; return false;
} }
dbg->debuggeeGlobal = debuggee;
vp->setObject(*obj); vp->setObject(*obj);
return true; return true;
} }

View File

@ -48,6 +48,7 @@
#include "jshashtable.h" #include "jshashtable.h"
#include "jswrapper.h" #include "jswrapper.h"
#include "jsvalue.h" #include "jsvalue.h"
#include "vm/GlobalObject.h"
namespace js { namespace js {
@ -57,7 +58,7 @@ class Debug {
private: private:
JSCList link; // See JSRuntime::debuggerList. JSCList link; // See JSRuntime::debuggerList.
JSObject *object; // The Debug object. Strong reference. JSObject *object; // The Debug object. Strong reference.
JSCompartment *debuggeeCompartment; // Weak reference. GlobalObject *debuggeeGlobal; // The debuggee. Cross-compartment weak reference.
JSObject *hooksObject; // See Debug.prototype.hooks. Strong reference. JSObject *hooksObject; // See Debug.prototype.hooks. Strong reference.
JSObject *uncaughtExceptionHook; // Strong reference. JSObject *uncaughtExceptionHook; // Strong reference.
bool enabled; bool enabled;
@ -67,10 +68,15 @@ class Debug {
bool hasDebuggerHandler; // hooks.debuggerHandler bool hasDebuggerHandler; // hooks.debuggerHandler
bool hasThrowHandler; // hooks.throw bool hasThrowHandler; // hooks.throw
// Weak references to stack frames that are currently on the stack
// and thus necessarily alive. (Removed in slowPathLeaveStackFrame.)
typedef HashMap<StackFrame *, JSObject *, DefaultHasher<StackFrame *>, SystemAllocPolicy> typedef HashMap<StackFrame *, JSObject *, DefaultHasher<StackFrame *>, SystemAllocPolicy>
FrameMap; FrameMap;
FrameMap frames; FrameMap frames;
// Keys are referents, values are Debug.Object objects. The combination of
// the a key being live and this Debug being live keeps the corresponding
// Debug.Object alive.
typedef HashMap<JSObject *, JSObject *, DefaultHasher<JSObject *>, SystemAllocPolicy> typedef HashMap<JSObject *, JSObject *, DefaultHasher<JSObject *>, SystemAllocPolicy>
ObjectMap; ObjectMap;
ObjectMap objects; ObjectMap objects;
@ -111,17 +117,18 @@ class Debug {
JSTrapStatus handleThrow(JSContext *cx, Value *vp); JSTrapStatus handleThrow(JSContext *cx, Value *vp);
public: public:
Debug(JSObject *dbg, JSObject *hooks, JSCompartment *compartment); Debug(JSObject *dbg, JSObject *hooks);
~Debug(); ~Debug();
bool init(); bool init(JSContext *cx);
inline JSObject *toJSObject() const; inline JSObject *toJSObject() const;
static inline Debug *fromJSObject(JSObject *obj); static inline Debug *fromJSObject(JSObject *obj);
static Debug *fromChildJSObject(JSObject *obj); static Debug *fromChildJSObject(JSObject *obj);
void removeDebuggee(GlobalObject *global, GlobalObjectSet::Enum *e);
static void detachFromCompartment(JSCompartment *comp);
/*********************************** Methods for interaction with the GC. */ /*********************************** Methods for interaction with the GC. */
//
// A Debug object is live if: // A Debug object is live if:
// * the Debug JSObject is live (Debug::trace handles this case); OR // * the Debug JSObject is live (Debug::trace handles this case); OR
// * it is in the middle of dispatching an event (the event dispatching // * it is in the middle of dispatching an event (the event dispatching
@ -138,9 +145,7 @@ class Debug {
// //
static bool mark(GCMarker *trc, JSCompartment *compartment, JSGCInvocationKind gckind); static bool mark(GCMarker *trc, JSCompartment *compartment, JSGCInvocationKind gckind);
static void sweepAll(JSRuntime *rt); static void sweepAll(JSRuntime *rt);
static void sweepCompartment(JSCompartment *compartment);
inline bool observesCompartment(JSCompartment *c) const;
void detachFrom(JSCompartment *c);
static inline void leaveStackFrame(JSContext *cx); static inline void leaveStackFrame(JSContext *cx);
static inline JSTrapStatus onDebuggerStatement(JSContext *cx, js::Value *vp); static inline JSTrapStatus onDebuggerStatement(JSContext *cx, js::Value *vp);
@ -188,6 +193,11 @@ class Debug {
// is false.) // is false.)
// //
bool newCompletionValue(AutoCompartment &ac, bool ok, Value val, Value *vp); bool newCompletionValue(AutoCompartment &ac, bool ok, Value val, Value *vp);
private:
// Prohibit copying.
Debug(const Debug &);
Debug & operator=(const Debug &);
}; };
bool bool
@ -199,7 +209,7 @@ Debug::hasAnyLiveHooks() const
bool bool
Debug::observesScope(JSObject *obj) const Debug::observesScope(JSObject *obj) const
{ {
return observesCompartment(obj->compartment()); return obj->getGlobal() == debuggeeGlobal;
} }
bool bool
@ -208,13 +218,6 @@ Debug::observesFrame(StackFrame *fp) const
return observesScope(&fp->scopeChain()); return observesScope(&fp->scopeChain());
} }
bool
Debug::observesCompartment(JSCompartment *c) const
{
JS_ASSERT(c);
return debuggeeCompartment == c;
}
JSObject * JSObject *
Debug::toJSObject() const Debug::toJSObject() const
{ {
@ -232,14 +235,14 @@ Debug::fromJSObject(JSObject *obj)
void void
Debug::leaveStackFrame(JSContext *cx) Debug::leaveStackFrame(JSContext *cx)
{ {
if (!cx->compartment->getDebuggers().empty()) if (!cx->compartment->getDebuggees().empty())
slowPathLeaveStackFrame(cx); slowPathLeaveStackFrame(cx);
} }
JSTrapStatus JSTrapStatus
Debug::onDebuggerStatement(JSContext *cx, js::Value *vp) Debug::onDebuggerStatement(JSContext *cx, js::Value *vp)
{ {
return cx->compartment->getDebuggers().empty() return cx->compartment->getDebuggees().empty()
? JSTRAP_CONTINUE ? JSTRAP_CONTINUE
: dispatchHook(cx, vp, : dispatchHook(cx, vp,
DebugObservesMethod(&Debug::observesDebuggerStatement), DebugObservesMethod(&Debug::observesDebuggerStatement),
@ -249,7 +252,7 @@ Debug::onDebuggerStatement(JSContext *cx, js::Value *vp)
JSTrapStatus JSTrapStatus
Debug::onThrow(JSContext *cx, js::Value *vp) Debug::onThrow(JSContext *cx, js::Value *vp)
{ {
return cx->compartment->getDebuggers().empty() return cx->compartment->getDebuggees().empty()
? JSTRAP_CONTINUE ? JSTRAP_CONTINUE
: dispatchHook(cx, vp, : dispatchHook(cx, vp,
DebugObservesMethod(&Debug::observesThrow), DebugObservesMethod(&Debug::observesThrow),

View File

@ -199,12 +199,10 @@ JS_SetDebugModeForCompartment(JSContext *cx, JSCompartment *comp, JSBool debug)
comp->debugMode = !!debug; comp->debugMode = !!debug;
// Detach any debuggers attached to this compartment. // Detach any debuggers attached to this compartment.
if (debug) { if (debug)
JS_ASSERT(comp->getDebuggers().empty()); JS_ASSERT(comp->getDebuggees().empty());
} else { else
while (!comp->getDebuggers().empty()) Debug::detachFromCompartment(comp);
comp->getDebuggers().back()->detachFrom(comp);
}
// Discard JIT code for any scripts that change debugMode. This function // Discard JIT code for any scripts that change debugMode. This function
// assumes that 'comp' is in the same thread as 'cx'. // assumes that 'comp' is in the same thread as 'cx'.

View File

@ -6643,7 +6643,7 @@ END_CASE(JSOP_ARRAYPUSH)
atoms = script->atomMap.vector; atoms = script->atomMap.vector;
/* Call debugger throw hook if set. */ /* Call debugger throw hook if set. */
if (cx->debugHooks->throwHook || !cx->compartment->getDebuggers().empty()) { if (cx->debugHooks->throwHook || !cx->compartment->getDebuggees().empty()) {
Value rval; Value rval;
JSTrapStatus st = Debug::onThrow(cx, &rval); JSTrapStatus st = Debug::onThrow(cx, &rval);
if (st == JSTRAP_CONTINUE) { if (st == JSTRAP_CONTINUE) {

View File

@ -503,7 +503,7 @@ js_InternalThrow(VMFrame &f)
for (;;) { for (;;) {
// Call the throw hook if necessary // Call the throw hook if necessary
JSThrowHook handler = cx->debugHooks->throwHook; JSThrowHook handler = cx->debugHooks->throwHook;
if (handler || !cx->compartment->getDebuggers().empty()) { if (handler || !cx->compartment->getDebuggees().empty()) {
Value rval; Value rval;
JSTrapStatus st = Debug::onThrow(cx, &rval); JSTrapStatus st = Debug::onThrow(cx, &rval);
if (st == JSTRAP_CONTINUE && handler) { if (st == JSTRAP_CONTINUE && handler) {

View File

@ -1169,7 +1169,7 @@ void JS_FASTCALL
stubs::Debugger(VMFrame &f, jsbytecode *pc) stubs::Debugger(VMFrame &f, jsbytecode *pc)
{ {
JSDebuggerHandler handler = f.cx->debugHooks->debuggerHandler; JSDebuggerHandler handler = f.cx->debugHooks->debuggerHandler;
if (handler || !f.cx->compartment->getDebuggers().empty()) { if (handler || !f.cx->compartment->getDebuggees().empty()) {
JSTrapStatus st = JSTRAP_CONTINUE; JSTrapStatus st = JSTRAP_CONTINUE;
Value rval; Value rval;
if (handler) { if (handler) {

View File

@ -201,4 +201,65 @@ GlobalObject::isEvalAllowed(JSContext *cx)
return !v.isFalse(); return !v.isFalse();
} }
void
GlobalDebuggees_finalize(JSContext *cx, JSObject *obj)
{
cx->delete_((GlobalObject::DebugVector *) obj->getPrivate());
}
static Class
GlobalDebuggees_class = {
"GlobalDebuggee", JSCLASS_HAS_PRIVATE,
PropertyStub, PropertyStub, PropertyStub, StrictPropertyStub,
EnumerateStub, ResolveStub, ConvertStub, GlobalDebuggees_finalize
};
GlobalObject::DebugVector *
GlobalObject::getDebuggers()
{
Value debuggers = getReservedSlot(DEBUGGERS);
if (debuggers.isUndefined())
return NULL;
JS_ASSERT(debuggers.toObject().clasp == &GlobalDebuggees_class);
return (DebugVector *) debuggers.toObject().getPrivate();
}
GlobalObject::DebugVector *
GlobalObject::getOrCreateDebuggers(JSContext *cx)
{
DebugVector *vec = getDebuggers();
if (vec)
return vec;
JSObject *obj = NewNonFunction<WithProto::Given>(cx, &GlobalDebuggees_class, NULL, NULL);
if (!obj)
return NULL;
vec = cx->new_<DebugVector>();
if (!vec)
return NULL;
obj->setPrivate(vec);
if (!js_SetReservedSlot(cx, this, DEBUGGERS, ObjectValue(*obj)))
return NULL;
return vec;
}
bool
GlobalObject::addDebug(JSContext *cx, Debug *dbg)
{
DebugVector *vec = getOrCreateDebuggers(cx);
if (!vec)
return false;
#ifdef DEBUG
for (Debug **p = vec->begin(); p != vec->end(); p++)
JS_ASSERT(*p != dbg);
#endif
if (vec->empty() && !compartment()->addDebuggee(this))
return false;
if (!vec->append(dbg)) {
compartment()->removeDebuggee(this);
return false;
}
return true;
}
} // namespace js } // namespace js

View File

@ -42,6 +42,8 @@
#define GlobalObject_h___ #define GlobalObject_h___
#include "jsfun.h" #include "jsfun.h"
#include "jsprvtd.h"
#include "jsvector.h"
extern JSObject * extern JSObject *
js_InitFunctionAndObjectClasses(JSContext *cx, JSObject *obj); js_InitFunctionAndObjectClasses(JSContext *cx, JSObject *obj);
@ -89,9 +91,10 @@ class GlobalObject : public ::JSObject {
static const uintN EVAL_ALLOWED = FUNCTION_NS + 1; static const uintN EVAL_ALLOWED = FUNCTION_NS + 1;
static const uintN EVAL = EVAL_ALLOWED + 1; static const uintN EVAL = EVAL_ALLOWED + 1;
static const uintN FLAGS = EVAL + 1; static const uintN FLAGS = EVAL + 1;
static const uintN DEBUGGERS = FLAGS + 1;
/* Total reserved-slot count for global objects. */ /* Total reserved-slot count for global objects. */
static const uintN RESERVED_SLOTS = FLAGS + 1; static const uintN RESERVED_SLOTS = DEBUGGERS + 1;
void staticAsserts() { void staticAsserts() {
/* /*
@ -150,8 +153,22 @@ class GlobalObject : public ::JSObject {
bool getFunctionNamespace(JSContext *cx, Value *vp); bool getFunctionNamespace(JSContext *cx, Value *vp);
bool initStandardClasses(JSContext *cx); bool initStandardClasses(JSContext *cx);
typedef js::Vector<js::Debug *, 0, js::SystemAllocPolicy> DebugVector;
// The collection of Debug objects debugging this global. If this global is
// not a debuggee, this returns either NULL or an empty vector.
DebugVector *getDebuggers();
// The same, but create the empty vector if one does not already
// exist. Returns NULL only on OOM.
DebugVector *getOrCreateDebuggers(JSContext *cx);
bool addDebug(JSContext *cx, Debug *dbg);
}; };
typedef HashSet<GlobalObject *, DefaultHasher<GlobalObject *>, SystemAllocPolicy> GlobalObjectSet;
} // namespace js } // namespace js
js::GlobalObject * js::GlobalObject *