Very rudimentary support for creating Debug.Frame objects, passing them to hooks, and cleaning them up afterwards.
authorJason Orendorff <jorendorff@mozilla.com>
Wed, 27 Apr 2011 18:22:28 -0500
changeset 74397 d806bd4f6a1ed852e8a151a297eff997cebc5f1d
parent 74396 20a64f3083771eebde13e2cb1ffcf9d4b6e2beec
child 74398 1443d370f7dca1461d9a8a8515803ab2fa3ec1bc
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
milestone6.0a1
Very rudimentary support for creating Debug.Frame objects, passing them to hooks, and cleaning them up afterwards.
js/src/jit-test/tests/debug/debug-object-20.js
js/src/jit-test/tests/debug/debug-object-22.js
js/src/jit-test/tests/debug/debug-object-23.js
js/src/jit-test/tests/debug/debug-object-24.js
js/src/jsdbg.cpp
js/src/jsdbg.h
js/src/jsdbgapi.cpp
js/src/jsgc.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
--- a/js/src/jit-test/tests/debug/debug-object-20.js
+++ b/js/src/jit-test/tests/debug/debug-object-20.js
@@ -3,17 +3,17 @@
 // hooks to be skipped.
 
 var g = newGlobal('new-compartment');
 var log;
 
 function makeDebug(g, name) {
     var dbg = new Debug(g);
     dbg.hooks = {
-        debuggerHandler: function () {
+        debuggerHandler: function (frame) {
             log += name;
             throw new Error(name);
         }
     };
     dbg.uncaughtExceptionHook = function (exc) {
         assertEq(exc.message, name);
         return name == "2" ? {return: 42} : undefined;
     };
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/debug-object-22.js
@@ -0,0 +1,51 @@
+// |jit-test| debug
+// Test .type and .generator fields of topmost stack frame passed to debuggerHandler.
+
+var g = newGlobal('new-compartment');
+g.debuggeeGlobal = this;
+g.eval("var hits;");
+g.eval("(" + function () {
+        var dbg = Debug(debuggeeGlobal);
+        dbg.hooks = {
+            debuggerHandler: function (f) {
+                assertEq(Object.getPrototypeOf(f), Debug.Frame.prototype);
+                assertEq(f.type, ftype);
+                assertEq(f.generator, fgen);
+                hits++;
+            }
+        };
+    } + ")()");
+
+g.ftype = "global";
+g.fgen = false;
+g.hits = 0;
+debugger;
+assertEq(g.hits, 1);
+
+g.ftype = "call";
+g.hits = 0;
+(function () { debugger; })();
+assertEq(g.hits, 1);
+
+g.ftype = "eval";
+g.hits = 0;
+eval("debugger;");
+assertEq(g.hits, 1);
+
+g.ftype = "eval";
+g.hits = 0;
+this.eval("debugger;");  // indirect eval
+assertEq(g.hits, 1);
+
+g.ftype = "eval";
+g.hits = 0;
+(function () { eval("debugger;"); })();
+assertEq(g.hits, 1);
+
+g.ftype = "call";
+g.fgen = true;
+g.hits = 0;
+function gen() { debugger; yield 1; debugger; }
+for (var x in gen()) {
+}
+assertEq(g.hits, 2);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/debug-object-23.js
@@ -0,0 +1,35 @@
+// |jit-test| debug
+// When the debugger is triggered twice from the same stack frame, the same
+// Debug.Frame object must be passed to the hook both times.
+
+var g = newGlobal('new-compartment');
+g.debuggeeGlobal = this;
+g.eval("var hits, frame;");
+g.eval("(" + function () {
+        var dbg = Debug(debuggeeGlobal);
+        dbg.hooks = {
+            debuggerHandler: function (f) {
+                if (hits++ == 0)
+                    frame = f;
+                else
+                    assertEq(f, frame);
+            }
+        };
+    } + ")()");
+
+g.hits = 0;
+debugger;
+debugger;
+assertEq(g.hits, 2);
+
+g.hits = 0;
+function f() {
+    debugger;
+    debugger;
+}
+f();
+assertEq(g.hits, 2);
+
+g.hits = 0;
+eval("debugger; debugger;");
+assertEq(g.hits, 2);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/debug-object-24.js
@@ -0,0 +1,26 @@
+// |jit-test| debug
+// When the debugger is triggered from different stack frames that happen to
+// occupy the same memory, it must deliver different Debug.Frame objects.
+
+var g = newGlobal('new-compartment');
+g.debuggeeGlobal = this;
+g.eval("var hits;");
+g.eval("(" + function () {
+        var a = [];
+        var dbg = Debug(debuggeeGlobal);
+        dbg.hooks = {
+            debuggerHandler: function (frame) {
+                for (var i = 0; i < a.length; i++) 
+                    assertEq(a[i] === frame, false);
+                a.push(frame);
+                hits++;
+            }
+        };
+    } + ")()");
+
+function f() { debugger; }
+function h() { debugger; f(); }
+g.hits = 0;
+for (var i = 0; i < 4; i++)
+    h();
+assertEq(g.hits, 8);
--- a/js/src/jsdbg.cpp
+++ b/js/src/jsdbg.cpp
@@ -44,16 +44,27 @@
 #include "jscntxt.h"
 #include "jsgcmark.h"
 #include "jsobj.h"
 #include "jswrapper.h"
 #include "jsobjinlines.h"
 
 using namespace js;
 
+// === Forward declarations
+
+extern Class Frame_class;
+
+enum {
+    JSSLOT_FRAME_OWNER,
+    JSSLOT_FRAME_COUNT
+};
+
+// === Utils
+
 static bool
 NotImplemented(JSContext *cx)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NEED_DIET, "API");
     return false;
 }
 
 bool
@@ -109,36 +120,83 @@ CheckThisClass(JSContext *cx, Value *vp,
     JSObject *thisobj =                                                      \
         CheckThisClass(cx, vp, &js::classname::jsclass, fnname);             \
     if (!thisobj)                                                            \
         return false;                                                        \
     js::classname *private = (classname *) thisobj->getPrivate();
 
 // === Debug hook dispatch
 
+enum {
+    JSSLOT_DEBUG_FRAME_PROTO,
+    JSSLOT_DEBUG_COUNT
+};
+
 Debug::Debug(JSObject *dbg, JSObject *hooks, JSCompartment *compartment)
   : object(dbg), debuggeeCompartment(compartment), hooksObject(hooks),
     uncaughtExceptionHook(NULL), enabled(true), hasDebuggerHandler(false)
 {
 }
 
+bool
+Debug::init()
+{
+    return frames.init();
+}
+
+bool
+Debug::getScriptFrame(JSContext *cx, JSStackFrame *fp, Value *vp)
+{
+    FrameMap::AddPtr p = frames.lookupForAdd(fp);
+    if (!p) {
+        JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject();
+        JSObject *frameobj = NewNonFunction<WithProto::Given>(cx, &Frame_class, proto, NULL);
+        if (!frameobj || !frameobj->ensureClassReservedSlots(cx))
+            return false;
+        frameobj->setPrivate(fp);
+        frameobj->setReservedSlot(JSSLOT_FRAME_OWNER, ObjectValue(*object));
+        if (!frames.add(p, fp, frameobj)) {
+            js_ReportOutOfMemory(cx);
+            return false;
+        }
+    }
+    vp->setObject(*p->value);
+    return true;
+}
+
+void
+Debug::slowPathLeaveStackFrame(JSContext *cx)
+{
+    JSStackFrame *fp = cx->regs->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);
+        }
+    }
+}
+
 JSTrapStatus
 Debug::handleUncaughtException(AutoCompartment &ac, Value *vp, bool callHook)
 {
     JSContext *cx = ac.context;
     if (cx->isExceptionPending()) {
         if (callHook && uncaughtExceptionHook) {
             Value fval = ObjectValue(*uncaughtExceptionHook);
             Value exc = cx->getPendingException();
             Value rv;
             cx->clearPendingException();
             if (ExternalInvoke(cx, ObjectValue(*object), fval, 1, &exc, &rv))
                 return parseResumptionValue(ac, true, rv, vp, false);
         }
- 
+
         if (cx->isExceptionPending()) {
             JS_ReportPendingException(cx);
             cx->clearPendingException();
         }
     }
     return JSTRAP_ERROR;
 }
 
@@ -204,24 +262,30 @@ CallMethodIfPresent(JSContext *cx, JSObj
            js_GetMethod(cx, obj, ATOM_TO_JSID(atom), JSGET_NO_METHOD_BARRIER, &fval) &&
            (!js_IsCallable(fval) ||
             ExternalInvoke(cx, ObjectValue(*obj), fval, argc, argv, rval));
 }
 
 JSTrapStatus
 Debug::handleDebuggerStatement(JSContext *cx, Value *vp)
 {
+    // Grab cx->regs->fp before pushing a dummy frame.
+    JSStackFrame *fp = cx->regs->fp;
+
     JS_ASSERT(hasDebuggerHandler);
     AutoCompartment ac(cx, hooksObject);
     if (!ac.enter())
         return JSTRAP_ERROR;
 
-    // XXX debuggerHandler should receive a Frame.
+    Value argv[1];
+    if (!getScriptFrame(cx, fp, argv))
+        return JSTRAP_ERROR;
+
     Value rv;
-    bool ok = CallMethodIfPresent(cx, hooksObject, "debuggerHandler", 0, NULL, &rv);
+    bool ok = CallMethodIfPresent(cx, hooksObject, "debuggerHandler", 1, argv, &rv);
     return parseResumptionValue(ac, ok, rv, vp);
 }
 
 JSTrapStatus
 Debug::dispatchDebuggerStatement(JSContext *cx, js::Value *vp)
 {
     // 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
@@ -247,17 +311,16 @@ Debug::dispatchDebuggerStatement(JSConte
             JSTrapStatus st = dbg->handleDebuggerStatement(cx, vp);
             if (st != JSTRAP_CONTINUE)
                 return st;
         }
     }
     return JSTRAP_CONTINUE;
 }
 
-
 // === Debug JSObjects
 
 bool
 Debug::mark(GCMarker *trc, JSCompartment *comp, JSGCInvocationKind gckind)
 {
     // Search for Debug objects in the given compartment. We do this by
     // searching all the compartments being debugged.
     bool markedAny = false;
@@ -294,16 +357,44 @@ Debug::mark(GCMarker *trc, JSCompartment
 
 void
 Debug::trace(JSTracer *trc, JSObject *obj)
 {
     if (Debug *dbg = (Debug *) obj->getPrivate()) {
         MarkObject(trc, *dbg->hooksObject, "hooks");
         if (dbg->uncaughtExceptionHook)
             MarkObject(trc, *dbg->uncaughtExceptionHook, "hooks");
+
+        // Mark Debug.Frame objects that are reachable from JS if we look them up
+        // again (because the corresponding JSStackFrame is still on the stack).
+        for (FrameMap::Enum e(dbg->frames); !e.empty(); e.popFront()) {
+            if (e.front().value->getPrivate())
+                MarkObject(trc, *obj, "live Debug.Frame");
+        }
+    }
+}
+
+void
+Debug::sweepAll(JSRuntime *rt)
+{
+    for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); c++)
+        sweepCompartment(*c);
+}
+
+void
+Debug::sweepCompartment(JSCompartment *compartment)
+{
+    // Sweep FrameMap entries for objects being collected.
+    const JSCompartment::DebugVector &debuggers = compartment->getDebuggers();
+    for (Debug **p = debuggers.begin(); p != debuggers.end(); p++) {
+        Debug *dbg = *p;
+        for (FrameMap::Enum e(dbg->frames); !e.empty(); e.popFront()) {
+            if (!e.front().value->isMarked())
+                e.removeFront();
+        }
     }
 }
 
 void
 Debug::finalize(JSContext *cx, JSObject *obj)
 {
     Debug *dbg = (Debug *) obj->getPrivate();
     if (dbg && dbg->debuggeeCompartment)
@@ -314,17 +405,17 @@ void
 Debug::detachFrom(JSCompartment *c)
 {
     JS_ASSERT(c == debuggeeCompartment);
     c->removeDebug(this);
     debuggeeCompartment = NULL;
 }
 
 Class Debug::jsclass = {
-    "Debug", JSCLASS_HAS_PRIVATE,
+    "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   */
     NULL,                 /* xdrObject   */
     NULL,                 /* hasInstance */
@@ -427,27 +518,30 @@ Debug::construct(JSContext *cx, uintN ar
     // Get Debug.prototype.
     Value v;
     jsid prototypeId = ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom);
     if (!vp[0].toObject().getProperty(cx, prototypeId, &v))
         return false;
     JSObject *proto = &v.toObject();
     JS_ASSERT(proto->getClass() == &Debug::jsclass);
 
-    // Make the new Debug object.
+    // Make the new Debug object. Each one has a reference to
+    // Debug.Frame.prototype in a reserved slot.
     JSObject *obj = NewNonFunction<WithProto::Given>(cx, &Debug::jsclass, proto, NULL);
-    if (!obj)
+    if (!obj || !obj->ensureClassReservedSlots(cx))
         return false;
+    obj->setReservedSlot(JSSLOT_DEBUG_FRAME_PROTO,
+                         proto->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO));
     JSObject *hooks = NewBuiltinClassInstance(cx, &js_ObjectClass);
     if (!hooks)
         return false;
     Debug *dbg = cx->new_<Debug>(obj, hooks, debuggeeCompartment);
     if (!dbg)
         return false;
-    if (!debuggeeCompartment->addDebug(dbg)) {
+    if (!dbg->init() || !debuggeeCompartment->addDebug(dbg)) {
         js_ReportOutOfMemory(cx);
         return false;
     }
     obj->setPrivate(dbg);
 
     vp->setObject(*obj);
     return true;
 }
@@ -455,19 +549,82 @@ Debug::construct(JSContext *cx, uintN ar
 JSPropertySpec Debug::properties[] = {
     JS_PSGS("hooks", Debug::getHooks, Debug::setHooks, 0),
     JS_PSGS("enabled", Debug::getEnabled, Debug::setEnabled, 0),
     JS_PSGS("uncaughtExceptionHook", Debug::getUncaughtExceptionHook,
             Debug::setUncaughtExceptionHook, 0),
     JS_PS_END
 };
 
+// === Debug.Frame
+
+Class Frame_class = {
+    "Frame", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_FRAME_COUNT),
+    PropertyStub, PropertyStub, PropertyStub, StrictPropertyStub,
+    EnumerateStub, ResolveStub, ConvertStub, FinalizeStub,
+};
+
+#define THIS_FRAME(cx, vp, fnname, thisobj, fp)                              \
+    JSObject *thisobj = CheckThisClass(cx, vp, &Frame_class, fnname);        \
+    if (!thisobj)                                                            \
+        return false;                                                        \
+    JSStackFrame *fp = (JSStackFrame *) thisobj->getPrivate()
+
+JSBool
+Frame_getType(JSContext *cx, uintN argc, Value *vp)
+{
+    THIS_FRAME(cx, vp, "get type", thisobj, fp);
+
+    // Indirect eval frames are both isGlobalFrame() and isEvalFrame(), so the
+    // order of checks here is significant.
+    vp->setString(fp->isEvalFrame()
+                  ? cx->runtime->atomState.evalAtom
+                  : fp->isGlobalFrame()
+                  ? cx->runtime->atomState.globalAtom
+                  : cx->runtime->atomState.callAtom);
+    return true;
+}
+
+JSBool
+Frame_getGenerator(JSContext *cx, uintN argc, Value *vp)
+{
+    THIS_FRAME(cx, vp, "get generator", thisobj, fp);
+    vp->setBoolean(fp->isGeneratorFrame());
+    return true;
+}
+
+JSBool
+Frame_construct(JSContext *cx, uintN argc, Value *vp)
+{
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_CONSTRUCTOR, "Debug.Frame");
+    return false;
+}
+
+JSPropertySpec Frame_properties[] = {
+    JS_PSG("type", Frame_getType, 0),
+    JS_PSG("generator", Frame_getGenerator, 0),
+    JS_PS_END
+};
+
+// === Glue
 
 extern JS_PUBLIC_API(JSBool)
 JS_DefineDebugObject(JSContext *cx, JSObject *obj)
 {
     JSObject *objProto;
     if (!js_GetClassPrototype(cx, obj, JSProto_Object, &objProto))
-        return NULL;
+        return false;
+
+    JSObject *debugCtor;
+    JSObject *debugProto = js_InitClass(cx, obj, objProto, &Debug::jsclass, Debug::construct, 1,
+                                        Debug::properties, NULL, NULL, NULL, &debugCtor);
+    if (!debugProto || !debugProto->ensureClassReservedSlots(cx))
+        return false;
 
-    return !!js_InitClass(cx, obj, objProto, &Debug::jsclass, Debug::construct, 1,
-                          Debug::properties, NULL, NULL, NULL);
+    JSObject *frameCtor;
+    JSObject *frameProto = js_InitClass(cx, debugCtor, objProto, &Frame_class, Frame_construct, 0,
+                                        Frame_properties, NULL, NULL, NULL, &frameCtor);
+    if (!frameProto)
+        return false;
+    debugProto->setReservedSlot(JSSLOT_DEBUG_FRAME_PROTO, ObjectValue(*frameProto));
+
+    return true;
 }
--- a/js/src/jsdbg.h
+++ b/js/src/jsdbg.h
@@ -40,16 +40,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef jsdbg_h__
 #define jsdbg_h__
 
 #include "jsapi.h"
 #include "jscompartment.h"
 #include "jsgc.h"
+#include "jshashtable.h"
 #include "jswrapper.h"
 #include "jsvalue.h"
 
 namespace js {
 
 class Debug {
     friend JSBool ::JS_DefineDebugObject(JSContext *cx, JSObject *obj);
 
@@ -59,16 +60,20 @@ class Debug {
     JSObject *hooksObject;  // See Debug.prototype.hooks. Strong reference.
     JSObject *uncaughtExceptionHook;  // Strong reference.
     bool enabled;
 
     // True if hooksObject had a debuggerHandler property when the hooks
     // property was set.
     bool hasDebuggerHandler;
 
+    typedef HashMap<JSStackFrame *, JSObject *, DefaultHasher<JSStackFrame *>, SystemAllocPolicy>
+        FrameMap;
+    FrameMap frames;
+
     JSTrapStatus handleUncaughtException(AutoCompartment &ac, Value *vp, bool callHook);
     JSTrapStatus parseResumptionValue(AutoCompartment &ac, bool ok, const Value &rv, Value *vp,
                                       bool callHook = true);
 
     static void trace(JSTracer *trc, JSObject *obj);
     static void finalize(JSContext *cx, JSObject *obj);
 
     static Class jsclass;
@@ -78,45 +83,53 @@ class Debug {
     static JSBool setEnabled(JSContext *cx, uintN argc, Value *vp);
     static JSBool getUncaughtExceptionHook(JSContext *cx, uintN argc, Value *vp);
     static JSBool setUncaughtExceptionHook(JSContext *cx, uintN argc, Value *vp);
     static JSBool construct(JSContext *cx, uintN argc, Value *vp);
     static JSPropertySpec properties[];
 
     inline bool hasAnyLiveHooks() const;
 
+    bool getScriptFrame(JSContext *cx, JSStackFrame *fp, Value *vp);
+    static void slowPathLeaveStackFrame(JSContext *cx);
+
     inline bool observesDebuggerStatement() const;
     static JSTrapStatus dispatchDebuggerStatement(JSContext *cx, Value *vp);
     JSTrapStatus handleDebuggerStatement(JSContext *cx, Value *vp);
 
   public:
     Debug(JSObject *dbg, JSObject *hooks, JSCompartment *compartment);
+    bool init();
+    inline JSObject *toJSObject() const;
+    static inline Debug *fromJSObject(JSObject *obj);
 
-    // Mark some Debug objects. A Debug object is live if:
+    // 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 this 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.
+    // 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);
-
-    inline JSObject *toJSObject() const;
-    static inline Debug *fromJSObject(JSObject *obj);
+    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 JSTrapStatus onDebuggerStatement(JSContext *cx, js::Value *vp);
 };
 
 bool
 Debug::hasAnyLiveHooks() const
 {
     return observesDebuggerStatement();
 }
@@ -137,16 +150,23 @@ Debug::toJSObject() const
 
 Debug *
 Debug::fromJSObject(JSObject *obj)
 {
     JS_ASSERT(obj->getClass() == &jsclass);
     return (Debug *) obj->getPrivate();
 }
 
+void
+Debug::leaveStackFrame(JSContext *cx)
+{
+    if (!cx->compartment->getDebuggers().empty())
+        slowPathLeaveStackFrame(cx);
+}
+
 bool
 Debug::observesDebuggerStatement() const
 {
     return enabled && hasDebuggerHandler;
 }
 
 JSTrapStatus
 Debug::onDebuggerStatement(JSContext *cx, js::Value *vp)
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -128,29 +128,31 @@ ScriptDebugPrologue(JSContext *cx, JSSta
     }
 
     Probes::enterJSFun(cx, fp->maybeFun(), fp->script());
 }
 
 bool
 ScriptDebugEpilogue(JSContext *cx, JSStackFrame *fp, bool okArg)
 {
+    JS_ASSERT(fp == cx->fp());
     JSBool ok = okArg;
 
     Probes::exitJSFun(cx, fp->maybeFun(), fp->script());
 
     if (void *hookData = fp->maybeHookData()) {
         if (fp->isFramePushedByExecute()) {
             if (JSInterpreterHook hook = cx->debugHooks->executeHook)
                 hook(cx, fp, false, &ok, hookData);
         } else {
             if (JSInterpreterHook hook = cx->debugHooks->callHook)
                 hook(cx, fp, false, &ok, hookData);
         }
     }
+    Debug::leaveStackFrame(cx);
 
     return ok;
 }
 
 } /* namespace js */
 
 #ifdef DEBUG
 static bool
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2522,16 +2522,21 @@ MarkAndSweep(JSContext *cx, JSCompartmen
     /* Finalize unreachable (key,value) pairs in all weak maps. */
     WeakMap::sweep(cx);
 
     js_SweepAtomState(cx);
 
     /* Finalize watch points associated with unreachable objects. */
     js_SweepWatchPoints(cx);
 
+    if (comp)
+        Debug::sweepCompartment(comp);
+    else
+        Debug::sweepAll(rt);
+
     /*
      * We finalize objects before other GC things to ensure that object's finalizer 
      * can access them even if they will be freed. Sweep the runtime's property trees 
      * after finalizing objects, in case any had watchpoints referencing tree nodes.
      * Do this before sweeping compartments, so that we sweep all shapes in
      * unreachable compartments.
      */
     if (comp) {
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3822,17 +3822,18 @@ DefineStandardSlot(JSContext *cx, JSObje
 
 namespace js {
 
 JSObject *
 DefineConstructorAndPrototype(JSContext *cx, JSObject *obj, JSProtoKey key, JSAtom *atom,
                               JSObject *protoProto, Class *clasp,
                               Native constructor, uintN nargs,
                               JSPropertySpec *ps, JSFunctionSpec *fs,
-                              JSPropertySpec *static_ps, JSFunctionSpec *static_fs)
+                              JSPropertySpec *static_ps, JSFunctionSpec *static_fs,
+                              JSObject **ctorp)
 {
     /*
      * Create a prototype object for this class.
      *
      * FIXME: lazy standard (built-in) class initialization and even older
      * eager boostrapping code rely on all of these properties:
      *
      * 1. NewObject attempting to compute a default prototype object when
@@ -3961,33 +3962,36 @@ DefineConstructorAndPrototype(JSContext 
         if (ctor && (clasp->flags & JSCLASS_FREEZE_CTOR) && !ctor->freeze(cx))
             goto bad;
     }
 
     /* If this is a standard class, cache its prototype. */
     if (key != JSProto_Null && !js_SetClassObject(cx, obj, key, ctor, proto))
         goto bad;
 
+    if (ctorp)
+        *ctorp = ctor;
     return proto;
 
 bad:
     if (named) {
         Value rval;
         obj->deleteProperty(cx, ATOM_TO_JSID(atom), &rval, false);
     }
     return NULL;
 }
 
 }
 
 JSObject *
 js_InitClass(JSContext *cx, JSObject *obj, JSObject *protoProto,
              Class *clasp, Native constructor, uintN nargs,
              JSPropertySpec *ps, JSFunctionSpec *fs,
-             JSPropertySpec *static_ps, JSFunctionSpec *static_fs)
+             JSPropertySpec *static_ps, JSFunctionSpec *static_fs,
+             JSObject **ctorp)
 {
     JSAtom *atom = js_Atomize(cx, clasp->name, strlen(clasp->name), 0);
     if (!atom)
         return NULL;
 
     /*
      * All instances of the class will inherit properties from the prototype
      * object we are about to create (in DefineConstructorAndPrototype), which
@@ -4003,17 +4007,17 @@ js_InitClass(JSContext *cx, JSObject *ob
     JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(clasp);
     if (key != JSProto_Null &&
         !protoProto &&
         !js_GetClassPrototype(cx, obj, JSProto_Object, &protoProto)) {
         return NULL;
     }
 
     return DefineConstructorAndPrototype(cx, obj, key, atom, protoProto, clasp, constructor, nargs,
-                                         ps, fs, static_ps, static_fs);
+                                         ps, fs, static_ps, static_fs, ctorp);
 }
 
 bool
 JSObject::allocSlots(JSContext *cx, size_t newcap)
 {
     uint32 oldcap = numSlots();
 
     JS_ASSERT(newcap >= oldcap && !hasSlotsArray());
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -658,16 +658,19 @@ struct JSObject : js::gc::Cell {
         JS_ASSERT(slot < capacity);
         slots[slot] = value;
     }
 
     inline void nativeSetSlot(uintN slot, const js::Value &value);
 
     inline js::Value getReservedSlot(uintN index) const;
 
+    /* Call this only after the appropriate ensure{Class,Instance}ReservedSlots call. */
+    inline void setReservedSlot(uintN index, const js::Value &v);
+
     /* Defined in jsscopeinlines.h to avoid including implementation dependencies here. */
     inline void updateShape(JSContext *cx);
     inline void updateFlags(const js::Shape *shape, bool isDefinitelyAtom = false);
 
     /* Extend this object to have shape as its last-added property. */
     inline void extend(JSContext *cx, const js::Shape *shape, bool isDefinitelyAtom = false);
 
     JSObject *getProto() const  { return proto; }
@@ -1543,24 +1546,26 @@ extern JSObject *
 js_InitObjectClass(JSContext *cx, JSObject *obj);
 
 namespace js {
 JSObject *
 DefineConstructorAndPrototype(JSContext *cx, JSObject *obj, JSProtoKey key, JSAtom *atom,
                               JSObject *protoProto, Class *clasp,
                               Native constructor, uintN nargs,
                               JSPropertySpec *ps, JSFunctionSpec *fs,
-                              JSPropertySpec *static_ps, JSFunctionSpec *static_fs);
+                              JSPropertySpec *static_ps, JSFunctionSpec *static_fs,
+                              JSObject **ctorp = NULL);
 }
 
 extern JSObject *
 js_InitClass(JSContext *cx, JSObject *obj, JSObject *parent_proto,
              js::Class *clasp, js::Native constructor, uintN nargs,
              JSPropertySpec *ps, JSFunctionSpec *fs,
-             JSPropertySpec *static_ps, JSFunctionSpec *static_fs);
+             JSPropertySpec *static_ps, JSFunctionSpec *static_fs,
+             JSObject **ctorp = NULL);
 
 /*
  * Select Object.prototype method names shared between jsapi.cpp and jsobj.cpp.
  */
 extern const char js_watch_str[];
 extern const char js_unwatch_str[];
 extern const char js_hasOwnProperty_str[];
 extern const char js_isPrototypeOf_str[];
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -293,16 +293,22 @@ JSObject::ensureClassReservedSlots(JSCon
 }
 
 inline js::Value
 JSObject::getReservedSlot(uintN index) const
 {
     return (index < numSlots()) ? getSlot(index) : js::UndefinedValue();
 }
 
+inline void
+JSObject::setReservedSlot(uintN index, const js::Value &v)
+{
+    setSlot(index, v);
+}
+
 inline bool
 JSObject::canHaveMethodBarrier() const
 {
     return isObject() || isFunction() || isPrimitive() || isDate();
 }
 
 inline bool
 JSObject::isPrimitive() const