Debuggees are globals, not compartments.
authorJason Orendorff <jorendorff@mozilla.com>
Mon, 23 May 2011 11:11:09 -0500
changeset 74436 516fb38281b7ea816c8e05c0949fb1f28a72e296
parent 74435 6b8f455e9a572b16aefebfd9441a6db1eb61de21
child 74437 020af8278a9fe792da04c161607db18473dcd69d
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
milestone6.0a1
Debuggees are globals, not compartments.
js/src/jit-test/tests/debug/debuggees-01.js
js/src/jsapi.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsdbg.cpp
js/src/jsdbg.h
js/src/jsdbgapi.cpp
js/src/jsinterp.cpp
js/src/methodjit/InvokeHelpers.cpp
js/src/methodjit/StubCalls.cpp
js/src/vm/GlobalObject.cpp
js/src/vm/GlobalObject.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/debuggees-01.js
@@ -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);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2017,17 +2017,17 @@ struct JSClass {
  * member initial value.  The "original ... value" verbiage is there because
  * in ECMA-262, global properties naming class objects are read/write and
  * deleteable, for the most part.
  *
  * Implementing this efficiently requires that global objects have classes
  * with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
  * 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                                                  \
     (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT))
 
 /* Fast access to the original value of each standard class's prototype. */
 #define JSCLASS_CACHED_PROTO_SHIFT      (JSCLASS_HIGH_FLAGS_SHIFT + 8)
 #define JSCLASS_CACHED_PROTO_WIDTH      8
 #define JSCLASS_CACHED_PROTO_MASK       JS_BITMASK(JSCLASS_CACHED_PROTO_WIDTH)
 #define JSCLASS_HAS_CACHED_PROTO(key)   ((key) << JSCLASS_CACHED_PROTO_SHIFT)
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -142,20 +142,21 @@ JSCompartment::init()
 #endif
 
     if (!backEdgeTable.init())
         return false;
 
 #ifdef JS_METHODJIT
     if (!(jaegerCompartment = rt->new_<mjit::JaegerCompartment>()))
         return false;
-    return jaegerCompartment->Initialize();
-#else
-    return true;
+    if (!jaegerCompartment->Initialize())
+        return false;
 #endif
+
+    return debuggees.init();
 }
 
 #ifdef JS_METHODJIT
 size_t
 JSCompartment::getMjitCodeSize() const
 {
     return jaegerCompartment->execAlloc()->getCodeSize();
 }
@@ -597,20 +598,8 @@ JSCompartment::incBackEdgeCount(jsbyteco
     return 1;  /* oom not reported by backEdgeTable, so ignore. */
 }
 
 bool
 JSCompartment::isAboutToBeCollected(JSGCInvocationKind gckind)
 {
     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");
-}
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -43,16 +43,17 @@
 #include "jscntxt.h"
 #include "jsgc.h"
 #include "jsmath.h"
 #include "jsobj.h"
 #include "jsfun.h"
 #include "jsgcstats.h"
 #include "jsclist.h"
 #include "jsxml.h"
+#include "vm/GlobalObject.h"
 
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4251) /* Silence warning about JS_FRIEND_API and data members. */
 #endif
 
 namespace JSC {
 
@@ -376,18 +377,16 @@ class DtoaCache {
         this->s = s;
     }
 
 };
 
 } /* namespace js */
 
 struct JS_FRIEND_API(JSCompartment) {
-    typedef js::Vector<js::Debug *, 0, js::SystemAllocPolicy> DebugVector;
-
     JSRuntime                    *rt;
     JSPrincipals                 *principals;
     js::gc::Chunk                *chunk;
 
     js::gc::ArenaList            arenas[js::gc::FINALIZE_LIMIT];
     js::gc::FreeLists            freeLists;
 
     uint32                       gcBytes;
@@ -513,34 +512,39 @@ struct JS_FRIEND_API(JSCompartment) {
 
     typedef js::HashMap<jsbytecode*,
                         size_t,
                         js::DefaultHasher<jsbytecode*>,
                         js::SystemAllocPolicy> BackEdgeMap;
 
     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; }
   public:
     js::MathCache *getMathCache(JSContext *cx) {
         return mathCache ? mathCache : allocMathCache(cx);
     }
 
     size_t backEdgeCount(jsbytecode *pc) const;
     size_t incBackEdgeCount(jsbytecode *pc);
 
-    const DebugVector &getDebuggers() const { return debuggers; }
-
-    bool addDebug(js::Debug *dbg) {
+    js::GlobalObjectSet &getDebuggees() { return debuggees; }
+    bool addDebuggee(js::GlobalObject *global) {
         JS_ASSERT(debugMode);
-        return debuggers.append(dbg);
+        return !!debuggees.put(global);
     }
-    void removeDebug(js::Debug *dbg);
+    void removeDebuggee(js::GlobalObject *global) {
+        debuggees.remove(global);
+    }
 };
 
 #define JS_SCRIPTS_TO_GC(cx)    ((cx)->compartment->scriptsToGC)
 #define JS_PROPERTY_TREE(cx)    ((cx)->compartment->propertyTree)
 
 #ifdef DEBUG
 #define JS_COMPARTMENT_METER(x) x
 #else
--- a/js/src/jsdbg.cpp
+++ b/js/src/jsdbg.cpp
@@ -131,37 +131,47 @@ CheckThisClass(JSContext *cx, Value *vp,
 // === Debug hook dispatch
 
 enum {
     JSSLOT_DEBUG_FRAME_PROTO,
     JSSLOT_DEBUG_OBJECT_PROTO,
     JSSLOT_DEBUG_COUNT
 };
 
-Debug::Debug(JSObject *dbg, JSObject *hooks, JSCompartment *compartment)
-  : object(dbg), debuggeeCompartment(compartment), hooksObject(hooks),
-    uncaughtExceptionHook(NULL), enabled(true), hasDebuggerHandler(false),
-    hasThrowHandler(false)
+Debug::Debug(JSObject *dbg, JSObject *hooks)
+  : object(dbg), debuggeeGlobal(NULL), hooksObject(hooks), uncaughtExceptionHook(NULL),
+    enabled(true), hasDebuggerHandler(false), hasThrowHandler(false)
 {
     // This always happens within a request on some cx.
-    AutoLockGC lock(compartment->rt);
-    JS_APPEND_LINK(&link, &compartment->rt->debuggerList);
+    JSRuntime *rt = dbg->compartment()->rt;
+    AutoLockGC lock(rt);
+    JS_APPEND_LINK(&link, &rt->debuggerList);
 }
 
 Debug::~Debug()
 {
+    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_ASSERT(object->compartment()->rt->gcRunning);
     JS_REMOVE_LINK(&link);
 }
 
 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));
 
 Debug *
 Debug::fromChildJSObject(JSObject *obj)
 {
     JS_ASSERT(obj->clasp == &DebugFrame_class || obj->clasp == &DebugObject_class);
@@ -211,24 +221,28 @@ Debug::getScriptFrame(JSContext *cx, Sta
     vp->setObject(*p->value);
     return true;
 }
 
 void
 Debug::slowPathLeaveStackFrame(JSContext *cx)
 {
     StackFrame *fp = cx->fp();
-    JSCompartment *compartment = cx->compartment;
-    const JSCompartment::DebugVector &debuggers = compartment->getDebuggers();
-    for (Debug **p = debuggers.begin(); p != debuggers.end(); p++) {
-        Debug *dbg = *p;
-        if (FrameMap::Ptr p = dbg->frames.lookup(fp)) {
-            JSObject *frameobj = p->value;
-            frameobj->setPrivate(NULL);
-            dbg->frames.remove(p);
+    GlobalObject *global = fp->scopeChain().getGlobal();
+
+    // 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;
+            if (FrameMap::Ptr p = dbg->frames.lookup(fp)) {
+                JSObject *frameobj = p->value;
+                frameobj->setPrivate(NULL);
+                dbg->frames.remove(p);
+            }
         }
     }
 }
 
 bool
 Debug::wrapDebuggeeValue(JSContext *cx, Value *vp)
 {
     assertSameCompartment(cx, object);
@@ -464,95 +478,108 @@ Debug::dispatchHook(JSContext *cx, js::V
                     DebugHandleMethod handleEvent)
 {
     // Determine which debuggers will receive this event, and in what order.
     // Make a copy of the list, since the original is mutable and we will be
     // calling into arbitrary JS.
     // Note: In the general case, 'triggered' contains references to objects in
     // different compartments--every compartment *except* this one.
     AutoValueVector triggered(cx);
-    JSCompartment *compartment = cx->compartment;
-    const JSCompartment::DebugVector &debuggers = compartment->getDebuggers();
-    for (Debug **p = debuggers.begin(); p != debuggers.end(); p++) {
-        Debug *dbg = *p;
-        if ((dbg->*observesEvent)()) {
-            if (!triggered.append(ObjectValue(*dbg->toJSObject())))
-                return JSTRAP_ERROR;
+    GlobalObject *global = cx->fp()->scopeChain().getGlobal();
+    if (GlobalObject::DebugVector *debuggers = global->getDebuggers()) {
+        for (Debug **p = debuggers->begin(); p != debuggers->end(); p++) {
+            Debug *dbg = *p;
+            if ((dbg->*observesEvent)()) {
+                if (!triggered.append(ObjectValue(*dbg->toJSObject())))
+                    return JSTRAP_ERROR;
+            }
         }
     }
 
     // Deliver the event to each debugger, checking again to make sure it
     // should still be delivered.
     for (Value *p = triggered.begin(); p != triggered.end(); p++) {
         Debug *dbg = Debug::fromJSObject(&p->toObject());
-        if (dbg->observesCompartment(compartment) && (dbg->*observesEvent)()) {
+        if (dbg->debuggeeGlobal == global && (dbg->*observesEvent)()) {
             JSTrapStatus st = (dbg->*handleEvent)(cx, vp);
             if (st != JSTRAP_CONTINUE)
                 return st;
         }
     }
     return JSTRAP_CONTINUE;
 }
 
 // === Debug JSObjects
 
 bool
 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
     // searching all the compartments being debugged.
-    bool markedAny = false;
     JSRuntime *rt = trc->context->runtime;
     for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); c++) {
         JSCompartment *dc = *c;
 
         // If comp is non-null, this is a per-compartment GC and we
         // search every dc, except for comp (since no compartment can
         // debug itself). If comp is null, this is a global GC and we
         // search every dc that is live.
         if (comp ? dc != comp : !dc->isAboutToBeCollected(gckind)) {
-            const JSCompartment::DebugVector &debuggers = dc->getDebuggers();
-            for (Debug **p = debuggers.begin(); p != debuggers.end(); p++) {
-                Debug *dbg = *p;
-                JSObject *obj = dbg->toJSObject();
+            const GlobalObjectSet &debuggees = dc->getDebuggees();
+            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;
+                    JSObject *obj = dbg->toJSObject();
 
-                // We only need to examine obj if it's in a compartment
-                // being GC'd and it isn't already marked.
-                if ((!comp || obj->compartment() == comp) && !obj->isMarked()) {
-                    if (dbg->hasAnyLiveHooks()) {
-                        // obj could be reachable only via its live, enabled
-                        // debugger hooks, which may yet be called.
-                        MarkObject(trc, *obj, "enabled Debug");
-                        markedAny = true;
+                    // We only need to examine obj if it's in a compartment
+                    // being GC'd and it isn't already marked.
+                    if ((!comp || obj->compartment() == comp) && !obj->isMarked()) {
+                        if (dbg->hasAnyLiveHooks()) {
+                            // obj could be reachable only via its live, enabled
+                            // debugger hooks, which may yet be called.
+                            MarkObject(trc, *obj, "enabled Debug");
+                            markedAny = true;
+                        }
                     }
-                }
 
-                // Handling Debug.Objects:
-                //
-                // If comp is the debuggee's compartment, do nothing. No
-                // referent objects will be collected, since we have a wrapper
-                // of each one.
-                //
-                // If comp is the debugger's compartment, mark all
-                // Debug.Objects, since the referents might be alive and
-                // therefore the table entries must remain.
-                //
-                // If comp is null, then for each key (referent-wrapper) that
-                // is marked, mark the corresponding value.
-                //
-                if (!comp || obj->compartment() == comp) {
-                    for (ObjectMap::Range r = dbg->objects.all(); !r.empty(); r.popFront()) {
-                        // The unwrap() call below has the following effect: we
-                        // mark the Debug.Object if the *referent* is alive,
-                        // even if the CCW of the referent seems unreachable.
-                        if (!r.front().value->isMarked() &&
-                            (comp || r.front().key->unwrap()->isMarked()))
-                        {
-                            MarkObject(trc, *r.front().value, "Debug.Object with live referent");
-                            markedAny = true;
+                    // Handling Debug.Objects:
+                    //
+                    // If comp is the debuggee's compartment, do nothing. No
+                    // referent objects will be collected, since we have a
+                    // wrapper of each one.
+                    //
+                    // If comp is the debugger's compartment, mark all
+                    // Debug.Objects, since the referents might be alive and
+                    // therefore the table entries must remain.
+                    //
+                    // If comp is null, then for each key (referent-wrapper)
+                    // that is marked, mark the corresponding value.
+                    //
+                    if (!comp || obj->compartment() == comp) {
+                        for (ObjectMap::Range r = dbg->objects.all(); !r.empty(); r.popFront()) {
+                            // The unwrap() call below has the following effect: we
+                            // mark the Debug.Object if the *referent* is alive,
+                            // even if the CCW of the referent seems unreachable.
+                            if (!r.front().value->isMarked() &&
+                                (comp || r.front().key->unwrap()->isMarked()))
+                            {
+                                MarkObject(trc, *r.front().value,
+                                           "Debug.Object with live referent");
+                                markedAny = true;
+                            }
                         }
                     }
                 }
             }
         }
     }
     return markedAny;
 }
@@ -573,44 +600,102 @@ Debug::trace(JSTracer *trc, JSObject *ob
             MarkObject(trc, *frameobj, "live Debug.Frame");
         }
     }
 }
 
 void
 Debug::sweepAll(JSRuntime *rt)
 {
-    // Sweep ObjectMap entries for objects being collected.
     for (JSCList *p = &rt->debuggerList; (p = JS_NEXT_LINK(p)) != &rt->debuggerList;) {
         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()) {
             JS_ASSERT(e.front().key->isMarked() == e.front().value->isMarked());
             if (!e.front().value->isMarked())
                 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
 Debug::finalize(JSContext *cx, JSObject *obj)
 {
     Debug *dbg = (Debug *) obj->getPrivate();
-    if (dbg && dbg->debuggeeCompartment)
-        dbg->detachFrom(dbg->debuggeeCompartment);
     cx->delete_(dbg);
 }
 
-void
-Debug::detachFrom(JSCompartment *c)
-{
-    JS_ASSERT(c == debuggeeCompartment);
-    c->removeDebug(this);
-    debuggeeCompartment = NULL;
-}
-
 Class Debug::jsclass = {
     "Debug", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUG_COUNT),
     PropertyStub, PropertyStub, PropertyStub, StrictPropertyStub,
     EnumerateStub, ResolveStub, ConvertStub, Debug::finalize,
     NULL,                 /* reserved0   */
     NULL,                 /* checkAccess */
     NULL,                 /* call        */
     NULL,                 /* construct   */
@@ -719,18 +804,18 @@ Debug::construct(JSContext *cx, uintN ar
         return ReportObjectRequired(cx);
     JSObject *argobj = &arg.toObject();
     if (!argobj->isCrossCompartmentWrapper()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CCW_REQUIRED, "Debug");
         return false;
     }
 
     // Check that the target compartment is in debug mode.
-    JSCompartment *debuggeeCompartment = argobj->getProxyPrivate().toObject().compartment();
-    if (!debuggeeCompartment->debugMode) {
+    GlobalObject *debuggee = argobj->getProxyPrivate().toObject().getGlobal();
+    if (!debuggee->compartment()->debugMode) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NEED_DEBUG_MODE);
         return false;
     }
 
     // Get Debug.prototype.
     Value v;
     jsid prototypeId = ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom);
     if (!vp[0].toObject().getProperty(cx, prototypeId, &v))
@@ -745,25 +830,25 @@ Debug::construct(JSContext *cx, uintN ar
         return false;
     for (uintN slot = JSSLOT_DEBUG_FRAME_PROTO; slot < JSSLOT_DEBUG_COUNT; slot++)
         obj->setReservedSlot(slot, proto->getReservedSlot(slot));
 
     JSObject *hooks = NewBuiltinClassInstance(cx, &js_ObjectClass);
     if (!hooks)
         return false;
 
-    Debug *dbg = cx->new_<Debug>(obj, hooks, debuggeeCompartment);
+    Debug *dbg = cx->new_<Debug>(obj, hooks);
     if (!dbg)
         return false;
     obj->setPrivate(dbg);
-    if (!dbg->init() || !debuggeeCompartment->addDebug(dbg)) {
-        js_ReportOutOfMemory(cx);
+    if (!dbg->init(cx) || !debuggee->addDebug(cx, dbg)) {
+        cx->delete_(dbg);
         return false;
     }
-
+    dbg->debuggeeGlobal = debuggee;
     vp->setObject(*obj);
     return true;
 }
 
 JSPropertySpec Debug::properties[] = {
     JS_PSGS("hooks", Debug::getHooks, Debug::setHooks, 0),
     JS_PSGS("enabled", Debug::getEnabled, Debug::setEnabled, 0),
     JS_PSGS("uncaughtExceptionHook", Debug::getUncaughtExceptionHook,
--- a/js/src/jsdbg.h
+++ b/js/src/jsdbg.h
@@ -43,39 +43,45 @@
 #define jsdbg_h__
 
 #include "jsapi.h"
 #include "jscompartment.h"
 #include "jsgc.h"
 #include "jshashtable.h"
 #include "jswrapper.h"
 #include "jsvalue.h"
+#include "vm/GlobalObject.h"
 
 namespace js {
 
 class Debug {
     friend JSBool ::JS_DefineDebugObject(JSContext *cx, JSObject *obj);
 
   private:
     JSCList link;                       // See JSRuntime::debuggerList.
     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 *uncaughtExceptionHook;    // Strong reference.
     bool enabled;
 
     // True if hooksObject had a property of the respective name when the hooks
     // property was set.
     bool hasDebuggerHandler;            // hooks.debuggerHandler
     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>
         FrameMap;
     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>
         ObjectMap;
     ObjectMap objects;
 
     JSTrapStatus handleUncaughtException(AutoCompartment &ac, Value *vp, bool callHook);
     JSTrapStatus parseResumptionValue(AutoCompartment &ac, bool ok, const Value &rv, Value *vp,
                                       bool callHook = true);
 
@@ -106,46 +112,45 @@ class Debug {
 
     bool observesDebuggerStatement() const;
     JSTrapStatus handleDebuggerStatement(JSContext *cx, Value *vp);
 
     bool observesThrow() const;
     JSTrapStatus handleThrow(JSContext *cx, Value *vp);
 
   public:
-    Debug(JSObject *dbg, JSObject *hooks, JSCompartment *compartment);
+    Debug(JSObject *dbg, JSObject *hooks);
     ~Debug();
 
-    bool init();
+    bool init(JSContext *cx);
     inline JSObject *toJSObject() const;
     static inline Debug *fromJSObject(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. */
 
-    //
     // A Debug object is live if:
     //   * the Debug JSObject is live (Debug::trace handles this case); OR
     //   * it is in the middle of dispatching an event (the event dispatching
     //     code roots it in this case); OR
     //   * it is enabled, and it is debugging at least one live compartment,
     //     and at least one of the following is true:
     //       - it has a debugger hook installed
     //       - it has a breakpoint set on a live script
     //       - it has a watchpoint set on a live object.
     //
     // The last case is handled by the mark() method. If it finds any Debug
     // objects that are definitely live but not yet marked, it marks them and
     // returns true. If not, it returns false.
     //
     static bool mark(GCMarker *trc, JSCompartment *compartment, JSGCInvocationKind gckind);
     static void sweepAll(JSRuntime *rt);
-
-    inline bool observesCompartment(JSCompartment *c) const;
-    void detachFrom(JSCompartment *c);
+    static void sweepCompartment(JSCompartment *compartment);
 
     static inline void leaveStackFrame(JSContext *cx);
     static inline JSTrapStatus onDebuggerStatement(JSContext *cx, js::Value *vp);
     static inline JSTrapStatus onThrow(JSContext *cx, js::Value *vp);
 
     /**************************************** Functions for use by jsdbg.cpp. */
 
     inline bool observesScope(JSObject *obj) const;
@@ -183,43 +188,41 @@ class Debug {
     // Postcondition: we are in the debugger compartment (ac is not entered)
     // whether creating the new completion value succeeded or not.
     //
     // On success, a completion value is in vp and ac.context does not have a
     // pending exception. (This ordinarily returns true even if the ok argument
     // is false.)
     //
     bool newCompletionValue(AutoCompartment &ac, bool ok, Value val, Value *vp);
+
+  private:
+    // Prohibit copying.
+    Debug(const Debug &);
+    Debug & operator=(const Debug &);
 };
 
 bool
 Debug::hasAnyLiveHooks() const
 {
     return observesDebuggerStatement();
 }
 
 bool
 Debug::observesScope(JSObject *obj) const
 {
-    return observesCompartment(obj->compartment());
+    return obj->getGlobal() == debuggeeGlobal;
 }
 
 bool
 Debug::observesFrame(StackFrame *fp) const
 {
     return observesScope(&fp->scopeChain());
 }
 
-bool
-Debug::observesCompartment(JSCompartment *c) const
-{
-    JS_ASSERT(c);
-    return debuggeeCompartment == c;
-}
-
 JSObject *
 Debug::toJSObject() const
 {
     JS_ASSERT(object);
     return object;
 }
 
 Debug *
@@ -227,34 +230,34 @@ Debug::fromJSObject(JSObject *obj)
 {
     JS_ASSERT(obj->getClass() == &jsclass);
     return (Debug *) obj->getPrivate();
 }
 
 void
 Debug::leaveStackFrame(JSContext *cx)
 {
-    if (!cx->compartment->getDebuggers().empty())
+    if (!cx->compartment->getDebuggees().empty())
         slowPathLeaveStackFrame(cx);
 }
 
 JSTrapStatus
 Debug::onDebuggerStatement(JSContext *cx, js::Value *vp)
 {
-    return cx->compartment->getDebuggers().empty()
+    return cx->compartment->getDebuggees().empty()
            ? JSTRAP_CONTINUE
            : dispatchHook(cx, vp,
                           DebugObservesMethod(&Debug::observesDebuggerStatement),
                           DebugHandleMethod(&Debug::handleDebuggerStatement));
 }
 
 JSTrapStatus
 Debug::onThrow(JSContext *cx, js::Value *vp)
 {
-    return cx->compartment->getDebuggers().empty()
+    return cx->compartment->getDebuggees().empty()
            ? JSTRAP_CONTINUE
            : dispatchHook(cx, vp,
                           DebugObservesMethod(&Debug::observesThrow),
                           DebugHandleMethod(&Debug::handleThrow));
 }
 
 }
 
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -194,22 +194,20 @@ JS_SetDebugModeForCompartment(JSContext 
     // incorrect to discard just the non-live scripts' JITScripts because they
     // might share ICs with live scripts (bug 632343).
     JS_ASSERT(!CompartmentHasLiveScripts(comp));
 
     // All scripts compiled from this point on should be in the requested debugMode.
     comp->debugMode = !!debug;
 
     // Detach any debuggers attached to this compartment.
-    if (debug) {
-        JS_ASSERT(comp->getDebuggers().empty());
-    } else {
-        while (!comp->getDebuggers().empty())
-            comp->getDebuggers().back()->detachFrom(comp);
-    }
+    if (debug)
+        JS_ASSERT(comp->getDebuggees().empty());
+    else
+        Debug::detachFromCompartment(comp);
 
     // Discard JIT code for any scripts that change debugMode. This function
     // assumes that 'comp' is in the same thread as 'cx'.
 #ifdef JS_METHODJIT
     JS::AutoEnterScriptCompartment ac;
 
     for (JSScript *script = (JSScript *)comp->scripts.next;
          &script->links != &comp->scripts;
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -6638,17 +6638,17 @@ END_CASE(JSOP_ARRAYPUSH)
         JSThrowHook handler;
         JSTryNote *tn, *tnlimit;
         uint32 offset;
 
         /* Restore atoms local in case we will resume. */
         atoms = script->atomMap.vector;
 
         /* Call debugger throw hook if set. */
-        if (cx->debugHooks->throwHook || !cx->compartment->getDebuggers().empty()) {
+        if (cx->debugHooks->throwHook || !cx->compartment->getDebuggees().empty()) {
             Value rval;
             JSTrapStatus st = Debug::onThrow(cx, &rval);
             if (st == JSTRAP_CONTINUE) {
                 handler = cx->debugHooks->throwHook;
                 if (handler)
                     st = handler(cx, script, regs.pc, Jsvalify(&rval), cx->debugHooks->throwHookData);
             }
 
--- a/js/src/methodjit/InvokeHelpers.cpp
+++ b/js/src/methodjit/InvokeHelpers.cpp
@@ -498,17 +498,17 @@ js_InternalThrow(VMFrame &f)
 
     // Make sure sp is up to date.
     JS_ASSERT(&cx->regs() == &f.regs);
 
     jsbytecode *pc = NULL;
     for (;;) {
         // Call the throw hook if necessary
         JSThrowHook handler = cx->debugHooks->throwHook;
-        if (handler || !cx->compartment->getDebuggers().empty()) {
+        if (handler || !cx->compartment->getDebuggees().empty()) {
             Value rval;
             JSTrapStatus st = Debug::onThrow(cx, &rval);
             if (st == JSTRAP_CONTINUE && handler) {
                 st = handler(cx, cx->fp()->script(), cx->regs().pc, Jsvalify(&rval),
                              cx->debugHooks->throwHookData);
             }
 
             switch (st) {
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -1164,17 +1164,17 @@ stubs::Mod(VMFrame &f)
         }
     }
 }
 
 void JS_FASTCALL
 stubs::Debugger(VMFrame &f, jsbytecode *pc)
 {
     JSDebuggerHandler handler = f.cx->debugHooks->debuggerHandler;
-    if (handler || !f.cx->compartment->getDebuggers().empty()) {
+    if (handler || !f.cx->compartment->getDebuggees().empty()) {
         JSTrapStatus st = JSTRAP_CONTINUE;
         Value rval;
         if (handler) {
             st = handler(f.cx, f.cx->fp()->script(), pc, Jsvalify(&rval),
                          f.cx->debugHooks->debuggerHandlerData);
         }
         if (st == JSTRAP_CONTINUE)
             st = Debug::onDebuggerStatement(f.cx, &rval);
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -196,9 +196,70 @@ GlobalObject::isEvalAllowed(JSContext *c
          * and that it permits eval(), then cache the result.
          */
         v.setBoolean((!callbacks || !callbacks->contentSecurityPolicyAllows) ||
                      callbacks->contentSecurityPolicyAllows(cx));
     }
     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
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -37,16 +37,18 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef GlobalObject_h___
 #define GlobalObject_h___
 
 #include "jsfun.h"
+#include "jsprvtd.h"
+#include "jsvector.h"
 
 extern JSObject *
 js_InitFunctionAndObjectClasses(JSContext *cx, JSObject *obj);
 
 namespace js {
 
 /*
  * Global object slots are reserved as follows:
@@ -84,19 +86,20 @@ class GlobalObject : public ::JSObject {
 
     /* One-off properties stored after slots for built-ins. */
     static const uintN THROWTYPEERROR        = STANDARD_CLASS_SLOTS;
     static const uintN REGEXP_STATICS        = THROWTYPEERROR + 1;
     static const uintN FUNCTION_NS           = REGEXP_STATICS + 1;
     static const uintN EVAL_ALLOWED          = FUNCTION_NS + 1;
     static const uintN EVAL                  = EVAL_ALLOWED + 1;
     static const uintN FLAGS                 = EVAL + 1;
+    static const uintN DEBUGGERS             = FLAGS + 1;
 
     /* Total reserved-slot count for global objects. */
-    static const uintN RESERVED_SLOTS = FLAGS + 1;
+    static const uintN RESERVED_SLOTS = DEBUGGERS + 1;
 
     void staticAsserts() {
         /*
          * The slot count must be in the public API for JSCLASS_GLOBAL_FLAGS,
          * and we aren't going to expose GlobalObject, so just assert that the
          * two values are synchronized.
          */
         JS_STATIC_ASSERT(JSCLASS_GLOBAL_SLOT_COUNT == RESERVED_SLOTS);
@@ -145,18 +148,32 @@ class GlobalObject : public ::JSObject {
         // confidently assert this.
         // JS_ASSERT(v.isUndefined());
         v.setObject(*evalobj);
     }
 
     bool getFunctionNamespace(JSContext *cx, Value *vp);
 
     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
 
 js::GlobalObject *
 JSObject::asGlobal()
 {
     JS_ASSERT(isGlobal());
     return reinterpret_cast<js::GlobalObject *>(this);
 }