diff --git a/js/src/jit-test/tests/debug/debuggees-01.js b/js/src/jit-test/tests/debug/debuggees-01.js new file mode 100644 index 00000000000..5e9be612871 --- /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); diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 991ecab4441..1f1f3e82c11 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -2022,7 +2022,7 @@ struct JSClass { * 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)) diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 4c7e1f11040..0d5013aab09 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -147,10 +147,11 @@ JSCompartment::init() #ifdef JS_METHODJIT if (!(jaegerCompartment = rt->new_())) return false; - return jaegerCompartment->Initialize(); -#else - return true; + if (!jaegerCompartment->Initialize()) + return false; #endif + + return debuggees.init(); } #ifdef JS_METHODJIT @@ -602,15 +603,3 @@ 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"); -} diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index c4afc475324..83cf89618f3 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -48,6 +48,7 @@ #include "jsgcstats.h" #include "jsclist.h" #include "jsxml.h" +#include "vm/GlobalObject.h" #ifdef _MSC_VER #pragma warning(push) @@ -381,8 +382,6 @@ class DtoaCache { } /* namespace js */ struct JS_FRIEND_API(JSCompartment) { - typedef js::Vector DebugVector; - JSRuntime *rt; JSPrincipals *principals; js::gc::Chunk *chunk; @@ -518,7 +517,11 @@ struct JS_FRIEND_API(JSCompartment) { 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: @@ -529,13 +532,14 @@ struct JS_FRIEND_API(JSCompartment) { 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 removeDebuggee(js::GlobalObject *global) { + debuggees.remove(global); } - void removeDebug(js::Debug *dbg); }; #define JS_SCRIPTS_TO_GC(cx) ((cx)->compartment->scriptsToGC) diff --git a/js/src/jsdbg.cpp b/js/src/jsdbg.cpp index 1c64efd9c8a..96a3cb0fdfd 100644 --- a/js/src/jsdbg.cpp +++ b/js/src/jsdbg.cpp @@ -136,27 +136,37 @@ enum { 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() { - // This always happens in the GC thread, so no locking is required. 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); } 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)); @@ -216,14 +226,18 @@ 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); + } } } } @@ -469,13 +483,14 @@ Debug::dispatchHook(JSContext *cx, js::Value *vp, DebugObservesMethod observesEv // 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; + } } } @@ -483,7 +498,7 @@ Debug::dispatchHook(JSContext *cx, js::Value *vp, DebugObservesMethod observesEv // 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; @@ -497,9 +512,13 @@ Debug::dispatchHook(JSContext *cx, js::Value *vp, DebugObservesMethod observesEv 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; @@ -509,47 +528,55 @@ Debug::mark(GCMarker *trc, JSCompartment *comp, JSGCInvocationKind gckind) // 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(); - // 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; - } - } + // 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(); - // 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"); + // 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; + } + } + } } } } @@ -578,34 +605,92 @@ Debug::trace(JSTracer *trc, JSObject *obj) 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, @@ -724,8 +809,8 @@ Debug::construct(JSContext *cx, uintN argc, Value *vp) } // 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; } @@ -750,15 +835,15 @@ Debug::construct(JSContext *cx, uintN argc, Value *vp) if (!hooks) return false; - Debug *dbg = cx->new_(obj, hooks, debuggeeCompartment); + Debug *dbg = cx->new_(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; } diff --git a/js/src/jsdbg.h b/js/src/jsdbg.h index cd39f3ef442..9a0a07da2b5 100644 --- a/js/src/jsdbg.h +++ b/js/src/jsdbg.h @@ -48,6 +48,7 @@ #include "jshashtable.h" #include "jswrapper.h" #include "jsvalue.h" +#include "vm/GlobalObject.h" namespace js { @@ -57,7 +58,7 @@ class Debug { 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; @@ -67,10 +68,15 @@ class Debug { 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, 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, SystemAllocPolicy> ObjectMap; ObjectMap objects; @@ -111,17 +117,18 @@ class Debug { 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 @@ -138,9 +145,7 @@ class Debug { // 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); @@ -188,6 +193,11 @@ class Debug { // is false.) // bool newCompletionValue(AutoCompartment &ac, bool ok, Value val, Value *vp); + + private: + // Prohibit copying. + Debug(const Debug &); + Debug & operator=(const Debug &); }; bool @@ -199,7 +209,7 @@ Debug::hasAnyLiveHooks() const bool Debug::observesScope(JSObject *obj) const { - return observesCompartment(obj->compartment()); + return obj->getGlobal() == debuggeeGlobal; } bool @@ -208,13 +218,6 @@ 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 { @@ -232,14 +235,14 @@ Debug::fromJSObject(JSObject *obj) 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), @@ -249,7 +252,7 @@ Debug::onDebuggerStatement(JSContext *cx, js::Value *vp) 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), diff --git a/js/src/jsdbgapi.cpp b/js/src/jsdbgapi.cpp index 075a876917a..b70c81d6e6b 100644 --- a/js/src/jsdbgapi.cpp +++ b/js/src/jsdbgapi.cpp @@ -199,12 +199,10 @@ JS_SetDebugModeForCompartment(JSContext *cx, JSCompartment *comp, JSBool debug) 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'. diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index 9f990e372d6..026fafba8b0 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -6643,7 +6643,7 @@ END_CASE(JSOP_ARRAYPUSH) 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) { diff --git a/js/src/methodjit/InvokeHelpers.cpp b/js/src/methodjit/InvokeHelpers.cpp index d9f2b3590c4..a1486f5bdd4 100644 --- a/js/src/methodjit/InvokeHelpers.cpp +++ b/js/src/methodjit/InvokeHelpers.cpp @@ -503,7 +503,7 @@ js_InternalThrow(VMFrame &f) 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) { diff --git a/js/src/methodjit/StubCalls.cpp b/js/src/methodjit/StubCalls.cpp index 3c2b8edcdda..7397351604f 100644 --- a/js/src/methodjit/StubCalls.cpp +++ b/js/src/methodjit/StubCalls.cpp @@ -1169,7 +1169,7 @@ 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) { diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index b137179b04b..df7cda6b467 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -201,4 +201,65 @@ GlobalObject::isEvalAllowed(JSContext *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(cx, &GlobalDebuggees_class, NULL, NULL); + if (!obj) + return NULL; + vec = cx->new_(); + 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 diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index 4108b0a65c0..c63859fc03d 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -42,6 +42,8 @@ #define GlobalObject_h___ #include "jsfun.h" +#include "jsprvtd.h" +#include "jsvector.h" extern JSObject * 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 = 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() { /* @@ -150,8 +153,22 @@ class GlobalObject : public ::JSObject { bool getFunctionNamespace(JSContext *cx, Value *vp); bool initStandardClasses(JSContext *cx); + + typedef js::Vector 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, SystemAllocPolicy> GlobalObjectSet; + } // namespace js js::GlobalObject *