Fix Debug.Frame.prototype.this for object this-values. Add Debug.Object.prototype.getClass. Make some stuff in jsdbg.cpp static.
authorJason Orendorff <jorendorff@mozilla.com>
Tue, 03 May 2011 14:04:17 -0500
changeset 74403 91bede82da8b277def96351adae811d3741e4519
parent 74402 1c8820681b80fd95a7979b5b14efcd78576369d5
child 74404 28ddba113a88c554864c86471a7dd37a614da14e
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
milestone6.0a1
Fix Debug.Frame.prototype.this for object this-values. Add Debug.Object.prototype.getClass. Make some stuff in jsdbg.cpp static.
js/src/jit-test/tests/debug/Frame-arguments-01.js
js/src/jit-test/tests/debug/Frame-this-01.js
js/src/jit-test/tests/debug/Frame-this-02.js
js/src/jit-test/tests/debug/Frame-this-03.js
js/src/jit-test/tests/debug/Frame-this-04.js
js/src/jit-test/tests/debug/Object-identity-03.js
js/src/jsdbg.cpp
js/src/jsdbg.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-01.js
@@ -0,0 +1,40 @@
+// |jit-test| debug
+// Frame.prototype.arguments with primitive values
+
+var g = newGlobal('new-compartment');
+g.args = null;
+var dbg = new Debug(g);
+var hits;
+var v;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+        hits++;
+        var args = frame.arguments;
+        assertEq(args, frame.arguments);
+        assertEq(args.length, g.args.length);
+        for (var i = 0; i < args.length; i++)
+            assertEq(args[i], g.args[i]);
+    }
+};
+
+// no formal parameters
+g.eval("function f() { debugger; }");
+
+hits = 0;
+g.eval("args = []; f();");
+g.eval("this.f();");
+g.eval("args = ['hello', 3.14, true, false, null, undefined]; " +
+       "f('hello', 3.14, true, false, null, undefined);");
+g.eval("args = [-0, NaN, -1/0]; this.f(-0, NaN, -1/0);");
+assertEq(hits, 4);
+
+// with formal parameters
+g.eval("function f(a, b) { debugger; }");
+
+hits = 0;
+g.eval("args = []; f();");
+g.eval("this.f();");
+g.eval("args = ['a', 'b']; f('a', 'b');");
+g.eval("this.f('a', 'b');");
+g.eval("f.bind(null, 'a')('b');");
+assertEq(hits, 5);
--- a/js/src/jit-test/tests/debug/Frame-this-01.js
+++ b/js/src/jit-test/tests/debug/Frame-this-01.js
@@ -1,15 +1,14 @@
 // |jit-test| debug
 // Frame.prototype.this in strict-mode functions, with primitive values
 
 var g = newGlobal('new-compartment');
 var dbg = new Debug(g);
 var hits = 0;
-var v;
 dbg.hooks = {
     debuggerHandler: function (frame) {
         hits++;
         assertEq(frame.this, g.v);
     }
 };
 
 g.eval("function f() { 'use strict'; debugger; }");
@@ -18,9 +17,9 @@ g.eval("Boolean.prototype.f = f; v = tru
 g.eval("f.call(v);");
 g.eval("Number.prototype.f = f; v = 3.14; v.f();");
 g.eval("f.call(v);");
 g.eval("String.prototype.f = f; v = 'hello'; v.f();");
 g.eval("f.call(v);");
 g.eval("v = undefined; f.call(v);");
 g.eval("v = null; f.call(v);");
 
-assertEq(hits, 8);
\ No newline at end of file
+assertEq(hits, 8);
--- a/js/src/jit-test/tests/debug/Frame-this-02.js
+++ b/js/src/jit-test/tests/debug/Frame-this-02.js
@@ -1,15 +1,14 @@
 // |jit-test| debug
 // Frame.prototype.this in strict direct eval frames
 
 var g = newGlobal('new-compartment');
 var dbg = new Debug(g);
 var hits = 0;
-var v;
 dbg.hooks = {
     debuggerHandler: function (frame) {
         hits++;
         assertEq(frame.this, g.v);
     }
 };
 
 g.eval("function f() { 'use strict'; eval('debugger;'); }");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-03.js
@@ -0,0 +1,30 @@
+// |jit-test| debug
+// Frame.prototype.this in non-strict-mode functions, with primitive values
+
+function classOf(obj) {
+    return Object.prototype.toString.call(obj).match(/^\[object (.*)\]$/)[1];
+}
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+        hits++;
+        assertEq(typeof frame.this, 'object');
+        assertEq(frame.this.getClass(), g.v == null ? classOf(g) : classOf(Object(g.v)));
+    }
+};
+
+g.eval("function f() { debugger; }");
+
+g.eval("Boolean.prototype.f = f; v = true; v.f();");
+g.eval("f.call(v);");
+g.eval("Number.prototype.f = f; v = 3.14; v.f();");
+g.eval("f.call(v);");
+g.eval("String.prototype.f = f; v = 'hello'; v.f();");
+g.eval("f.call(v);");
+g.eval("v = undefined; f.call(v);");
+g.eval("v = null; f.call(v);");
+
+assertEq(hits, 8);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-this-04.js
@@ -0,0 +1,28 @@
+// |jit-test| debug
+// Debug.Frame.prototype.this in functions, with object values
+
+function classOf(obj) {
+    return Object.prototype.toString.call(obj).match(/^\[object (.*)\]$/)[1];
+}
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+        hits++;
+        assertEq(typeof frame.this, 'object');
+        assertEq(frame.this.getClass(), classOf(Object(g.v)));
+    }
+};
+
+g.eval("function f() { debugger; }");
+
+g.eval("v = {}; f.call(v);");
+g.eval("v.f = f; v.f();");
+g.eval("v = new Date; f.call(v);");
+g.eval("v.f = f; v.f();");
+g.eval("v = []; f.call(v);");
+g.eval("Object.prototype.f = f; v.f();");
+g.eval("v = this; f();");
+assertEq(hits, 7);
--- a/js/src/jit-test/tests/debug/Object-identity-03.js
+++ b/js/src/jit-test/tests/debug/Object-identity-03.js
@@ -8,15 +8,19 @@ var dbg = Debug(g);
 var wrappers = [];
 
 dbg.hooks = {debuggerHandler: function (frame) { wrappers.push(frame.arguments[0]); }};
 g.eval("var originals = []; function f(x) { originals.push(x); debugger; }");
 for (var i = 0; i < N; i++)
     g.eval("f({});");
 assertEq(wrappers.length, N);
 
+for (var i = 0; i < N; i++)
+    for (var j = i + 1; j < N; j++)
+        assertEq(wrappers[i] === wrappers[j], false);
+
 gc();
 
 dbg.hooks = {debuggerHandler: function (frame) { assertEq(frame.arguments[0], wrappers.pop()); }};
 g.eval("function h(x) { debugger; }");
 for (var i = 0; i < N; i++)
     g.eval("h(originals.pop());");
 assertEq(wrappers.length, 0);
--- a/js/src/jsdbg.cpp
+++ b/js/src/jsdbg.cpp
@@ -40,16 +40,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "jsdbg.h"
 #include "jsapi.h"
 #include "jscntxt.h"
 #include "jsgcmark.h"
 #include "jsobj.h"
 #include "jswrapper.h"
+#include "jsinterpinlines.h"
 #include "jsobjinlines.h"
 #include "vm/Stack-inl.h"
 
 using namespace js;
 
 // === Forward declarations
 
 extern Class DebugFrame_class;
@@ -676,32 +677,32 @@ JSPropertySpec Debug::properties[] = {
 // === Debug.Frame
 
 Class DebugFrame_class = {
     "Frame", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGFRAME_COUNT),
     PropertyStub, PropertyStub, PropertyStub, StrictPropertyStub,
     EnumerateStub, ResolveStub, ConvertStub, FinalizeStub,
 };
 
-JSObject *
+static JSObject *
 CheckThisFrame(JSContext *cx, Value *vp, const char *fnname, bool checkLive)
 {
     if (!vp[1].isObject()) {
         ReportObjectRequired(cx);
         return NULL;
     }
     JSObject *thisobj = &vp[1].toObject();
     if (thisobj->getClass() != &DebugFrame_class) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
                              "Debug.Frame", fnname, thisobj->getClass()->name);
         return NULL;
     }
 
-    // Check for e.g. Debug.prototype, which is of the Debug JSClass but isn't
-    // really a Debug object.
+    // Check for Debug.Frame.prototype, which is of class DebugFrame_class but
+    // isn't really a working Debug.Frame object.
     if (!thisobj->getPrivate()) {
         if (thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_OWNER).isUndefined()) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
                                  "Debug.Frame", fnname, "prototype object");
             return NULL;
         }
         if (checkLive) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_FRAME_NOT_LIVE,
@@ -713,98 +714,166 @@ CheckThisFrame(JSContext *cx, Value *vp,
 }
 
 #define THIS_FRAME(cx, vp, fnname, thisobj, fp)                              \
     JSObject *thisobj = CheckThisFrame(cx, vp, fnname, true);                \
     if (!thisobj)                                                            \
         return false;                                                        \
     StackFrame *fp = (StackFrame *) thisobj->getPrivate()
 
-JSBool
+static JSBool
 DebugFrame_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
+static JSBool
 DebugFrame_getGenerator(JSContext *cx, uintN argc, Value *vp)
 {
     THIS_FRAME(cx, vp, "get generator", thisobj, fp);
     vp->setBoolean(fp->isGeneratorFrame());
     return true;
 }
 
-JSBool
+static JSBool
 DebugFrame_getThis(JSContext *cx, uintN argc, Value *vp)
 {
     THIS_FRAME(cx, vp, "get this", thisobj, fp);
-    *vp = fp->thisValue();
+    Value thisv;
+    {
+        AutoCompartment ac(cx, &fp->scopeChain());
+        if (!ac.enter())
+            return false;
+        if (!ComputeThis(cx, fp))
+            return false;
+        thisv = fp->thisValue();
+        ac.leave();
+    }
+    Debug *dbg = Debug::fromJSObject(&thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_OWNER).toObject());
+    if (!dbg->wrapDebuggeeValue(cx, &thisv))
+        return false;
+    *vp = thisv;
     return true;
 }
 
 JSBool
 DebugFrame_getArguments(JSContext *cx, uintN argc, Value *vp)
 {
     THIS_FRAME(cx, vp, "get arguments", thisobj, fp);
     (void) fp;  // quell warning that fp is unused
     *vp = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ARGUMENTS);
     return true;
 }
 
-JSBool
+static JSBool
 DebugFrame_getLive(JSContext *cx, uintN argc, Value *vp)
 {
     JSObject *thisobj = CheckThisFrame(cx, vp, "get live", false);
     if (!thisobj)
         return false;
     StackFrame *fp = (StackFrame *) thisobj->getPrivate();
     vp->setBoolean(!!fp);
     return true;
 }
 
-JSBool
+static JSBool
 DebugFrame_construct(JSContext *cx, uintN argc, Value *vp)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_CONSTRUCTOR, "Debug.Frame");
     return false;
 }
 
-JSPropertySpec DebugFrame_properties[] = {
+static JSPropertySpec DebugFrame_properties[] = {
     JS_PSG("type", DebugFrame_getType, 0),
     JS_PSG("generator", DebugFrame_getGenerator, 0),
     JS_PSG("this", DebugFrame_getThis, 0),
     JS_PSG("arguments", DebugFrame_getArguments, 0),
     JS_PSG("live", DebugFrame_getLive, 0),
     JS_PS_END
 };
 
 // === Debug.Object
 
 Class DebugObject_class = {
     "Object", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGFRAME_COUNT),
     PropertyStub, PropertyStub, PropertyStub, StrictPropertyStub,
     EnumerateStub, ResolveStub, ConvertStub, FinalizeStub,
 };
 
-JSBool
+static JSObject *
+DebugObject_checkThis(JSContext *cx, Value *vp, const char *fnname)
+{
+    if (!vp[1].isObject()) {
+        ReportObjectRequired(cx);
+        return NULL;
+    }
+    JSObject *thisobj = &vp[1].toObject();
+    if (thisobj->getClass() != &DebugObject_class) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
+                             "Debug.Object", fnname, thisobj->getClass()->name);
+        return NULL;
+    }
+
+    // Check for Debug.Object.prototype, which is of class DebugObject_class
+    // but isn't really a working Debug.Object.
+    if (thisobj->getReservedSlot(JSSLOT_DEBUGOBJECT_CCW).isUndefined()) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
+                             "Debug.Object", fnname, "prototype object");
+        return NULL;
+    }
+    return thisobj;
+}
+
+#define THIS_DEBUGOBJECT_CCW(cx, vp, fnname, ccwobj)                         \
+    JSObject *ccwobj = DebugObject_checkThis(cx, vp, fnname);                \
+    if (!ccwobj)                                                             \
+        return false;                                                        \
+    ccwobj = &ccwobj->getReservedSlot(JSSLOT_DEBUGOBJECT_CCW).toObject()
+
+#define THIS_DEBUGOBJECT_REFERENT(cx, vp, fnname, refobj)                    \
+    THIS_DEBUGOBJECT_CCW(cx, vp, fnname, refobj);                            \
+    if (refobj->isWrapper()) /* XXX TODO would love to assert instead */     \
+        refobj = JSWrapper::wrappedObject(refobj);                           \
+    else if (!(cx)->compartment->wrap(cx, &refobj))                          \
+        return false
+
+static JSBool
 DebugObject_construct(JSContext *cx, uintN argc, Value *vp)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_CONSTRUCTOR, "Debug.Object");
     return false;
 }
 
+static JSBool
+DebugObject_getClass(JSContext *cx, uintN argc, Value *vp)
+{
+    THIS_DEBUGOBJECT_REFERENT(cx, vp, "getClass", refobj);
+    const char *s = refobj->clasp->name;
+    JSAtom *str = js_Atomize(cx, s, strlen(s), 0);
+    if (!str)
+        return false;
+    vp->setString(str);
+    return true;
+}
+
+static JSFunctionSpec DebugObject_methods[] = {
+    JS_FN("getClass", DebugObject_getClass, 0, 0),
+    JS_FS_END
+};
+
+
 // === Glue
 
 extern JS_PUBLIC_API(JSBool)
 JS_DefineDebugObject(JSContext *cx, JSObject *obj)
 {
     JSObject *objProto;
     if (!js_GetClassPrototype(cx, obj, JSProto_Object, &objProto))
         return false;
@@ -818,16 +887,17 @@ JS_DefineDebugObject(JSContext *cx, JSOb
     JSObject *frameCtor;
     JSObject *frameProto = js_InitClass(cx, debugCtor, objProto, &DebugFrame_class,
                                         DebugFrame_construct, 0,
                                         DebugFrame_properties, NULL, NULL, NULL, &frameCtor);
     if (!frameProto)
         return false;
 
     JSObject *objectProto = js_InitClass(cx, debugCtor, objProto, &DebugObject_class,
-                                         DebugObject_construct, 0, NULL, NULL, NULL, NULL);
+                                         DebugObject_construct, 0,
+                                         NULL, DebugObject_methods, NULL, NULL);
     if (!objectProto)
         return false;
 
     debugProto->setReservedSlot(JSSLOT_DEBUG_FRAME_PROTO, ObjectValue(*frameProto));
     debugProto->setReservedSlot(JSSLOT_DEBUG_OBJECT_PROTO, ObjectValue(*objectProto));
     return true;
 }
--- a/js/src/jsdbg.h
+++ b/js/src/jsdbg.h
@@ -90,36 +90,16 @@ class Debug {
     static JSBool construct(JSContext *cx, uintN argc, Value *vp);
     static JSPropertySpec properties[];
 
     inline bool hasAnyLiveHooks() const;
 
     bool getScriptFrame(JSContext *cx, StackFrame *fp, Value *vp);
     static void slowPathLeaveStackFrame(JSContext *cx);
 
-    // Precondition: *vp is a value from a debuggee compartment and cx is in
-    // the debugger's compartment.
-    //
-    // Wrap *vp for the debugger compartment, wrap it in a Debug.Object if it's
-    // an object, store the result in *vp, and return true.
-    //
-    bool wrapDebuggeeValue(JSContext *cx, Value *vp);
-
-    // Inverse of wrapDebuggeeValue.
-    //
-    // Precondition: cx is in a debuggee compartment.
-    //
-    // If *vp is a Debug.Object, store the referent in *vp, appropriately
-    // rewrapped for the debuggee's compartment, regardless of what compartment
-    // the actual referent inhabits. Otherwise, if *vp is an object, throw a
-    // TypeError, because it is not a debuggee value. Otherwise *vp is a
-    // primitive, so copy it to the debuggee's compartment.
-    //
-    bool unwrapDebuggeeValue(JSContext *cx, Value *vp);
-
     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;
@@ -145,16 +125,37 @@ class Debug {
     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);
+
+    // Precondition: *vp is a value from a debuggee compartment and cx is in
+    // the debugger's compartment.
+    //
+    // Wrap *vp for the debugger compartment, wrap it in a Debug.Object if it's
+    // an object, store the result in *vp, and return true.
+    //
+    bool wrapDebuggeeValue(JSContext *cx, Value *vp);
+
+    // Inverse of wrapDebuggeeValue.
+    //
+    // Precondition: cx is in a debuggee compartment.
+    //
+    // If *vp is a Debug.Object, store the referent in *vp, appropriately
+    // rewrapped for the debuggee's compartment, regardless of what compartment
+    // the actual referent inhabits. Otherwise, if *vp is an object, throw a
+    // TypeError, because it is not a debuggee value. Otherwise *vp is a
+    // primitive, so copy it to the debuggee's compartment.
+    //
+    bool unwrapDebuggeeValue(JSContext *cx, Value *vp);
+
 };
 
 bool
 Debug::hasAnyLiveHooks() const
 {
     return observesDebuggerStatement();
 }