Make frame.arguments live.
authorJason Orendorff <jorendorff@mozilla.com>
Tue, 24 May 2011 14:31:39 -0500
changeset 74448 1d0b71fb4f677a64fb69fc365fd2598feee37a61
parent 74447 26d722c9da86ceabb048c8b23bca9a8d5470d26a
child 74449 2c50ea28d5b625728dd763e587d4a4fc57538fc9
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
milestone6.0a1
Make frame.arguments live.
js/src/jit-test/tests/debug/Frame-arguments-01.js
js/src/jit-test/tests/debug/Frame-arguments-02.js
js/src/jit-test/tests/debug/Frame-arguments-03.js
js/src/jit-test/tests/debug/Frame-arguments-04.js
js/src/jit-test/tests/debug/Frame-arguments-05.js
js/src/jit-test/tests/debug/Frame-arguments-06.js
js/src/jsdbg.cpp
--- a/js/src/jit-test/tests/debug/Frame-arguments-01.js
+++ b/js/src/jit-test/tests/debug/Frame-arguments-01.js
@@ -18,20 +18,20 @@ dbg.hooks = {
 };
 
 // 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 = ['hello', 3.14, true, false, null, undefined]; f.apply(undefined, args);");
+g.eval("f('hello', 3.14, true, false, null, undefined);");
 g.eval("args = [-0, NaN, -1/0]; this.f(-0, NaN, -1/0);");
-assertEq(hits, 4);
+assertEq(hits, 5);
 
 // 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');");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-02.js
@@ -0,0 +1,22 @@
+// |jit-test| debug
+// Object arguments.
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+        var args = frame.arguments;
+        assertEq(args, frame.arguments);
+        assertEq(args instanceof Array, true);
+        assertEq(args.length, 2);
+        assertEq(args[0] instanceof Debug.Object, true);
+        assertEq(args[0].class, args[1]);
+        hits++;
+    }
+};
+
+g.eval("function f(obj, cls) { debugger; }");
+g.eval("f({}, 'Object');");
+g.eval("f(Date, 'Function');");
+assertEq(hits, 2);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-03.js
@@ -0,0 +1,37 @@
+// |jit-test| debug
+// Destructuring arguments.
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+        var args = frame.arguments;
+        assertEq(args[0], 1);
+	assertEq(args.length, 4);
+
+        assertEq(args[1] instanceof Debug.Object, true);
+        assertEq(args[1].class, "Array");
+        var getprop = frame.eval("(function (p) { return this[p]; })").return;
+	assertEq(getprop instanceof Debug.Object, true);
+        assertEq(getprop.apply(args[1], ["length"]).return, 2);
+        assertEq(getprop.apply(args[1], [0]).return, 2);
+        assertEq(getprop.apply(args[1], [1]).return, 3);
+
+        assertEq(args[2] instanceof Debug.Object, true);
+        assertEq(args[2].class, "Object");
+        var x = getprop.apply(args[2], ["x"]).return;
+        assertEq(x.class, "Array"); 
+        assertEq(getprop.apply(x, ["0"]).return, 4);
+        assertEq(getprop.apply(args[2], ["z"]).return, 5);
+
+        assertEq(args[3] instanceof Debug.Object, true);
+        assertEq(args[3].class, "Object");
+        assertEq(getprop.apply(args[3], ["q"]).return, 6);
+        hits++;
+    }
+};
+
+g.eval("function f(a, [b, c], {x: [y], z: w}, {q}) { debugger; }");
+g.eval("f(1, [2, 3], {x: [4], z: 5}, {q: 6});");
+assertEq(hits, 1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-04.js
@@ -0,0 +1,21 @@
+// |jit-test| debug
+// frame.arguments works for all live frames
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var hits = 0;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+        for (var i = 0; i <= 4; i++) {
+            assertEq(frame.arguments.length, 1);
+            assertEq(frame.arguments[0], i);
+            frame = frame.older;
+        }
+        assertEq(frame, null);
+        hits++;
+    }
+};
+
+g.eval("function f(n) { if (n == 0) debugger; else f(n - 1); }");
+g.f(4);
+assertEq(hits, 1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-05.js
@@ -0,0 +1,15 @@
+// |jit-test| debug
+// frame.arguments is "live" (it reflects assignments to arguments).
+
+var g = newGlobal('new-compartment');
+var dbg = new Debug(g);
+var log = '';
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+	log += frame.arguments[0];
+    }
+};
+
+g.eval("function f(x) { x = '2'; debugger; x = '3'; debugger; }");
+g.f("1");
+assertEq(log, "23");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-arguments-06.js
@@ -0,0 +1,41 @@
+// |jit-test| debug
+// Test extracting frame.arguments element getters and calling them in
+// various awkward ways.
+
+load(libdir + "asserts.js");
+
+var g = newGlobal('new-compartment');
+var dbg = Debug(g);
+var hits = 0;
+var fframe, farguments, fgetter;
+dbg.hooks = {
+    debuggerHandler: function (frame) {
+	if (hits === 0) {
+	    fframe = frame;
+	    farguments = frame.arguments;
+	    fgetter = Object.getOwnPropertyDescriptor(farguments, "0").get;
+	    assertEq(fgetter instanceof Function, true);
+
+	    // Calling the getter without an appropriate this-object
+	    // fails, but shouldn't assert or crash.
+	    assertThrowsInstanceOf(function () { fgetter.call(Math); }, TypeError);
+	} else {
+	    // Since fframe is still on the stack, fgetter can be applied to it.
+	    assertEq(fframe.live, true);
+	    assertEq(fgetter.call(farguments), 100);
+
+	    // Since h was called without arguments, there is no argument 0.
+	    assertEq(fgetter.call(frame.arguments), undefined);
+	}
+	hits++;
+    }
+};
+
+g.eval("function h() { debugger; }");
+g.eval("function f(x) { debugger; h(); }");
+g.f(100);
+assertEq(hits, 2);
+
+// Now that fframe is no longer live, trying to get its arguments should throw.
+assertEq(fframe.live, false);
+assertThrowsInstanceOf(function () { fgetter.call(farguments); }, Error);
--- a/js/src/jsdbg.cpp
+++ b/js/src/jsdbg.cpp
@@ -56,16 +56,23 @@ using namespace js;
 extern Class DebugFrame_class;
 
 enum {
     JSSLOT_DEBUGFRAME_OWNER,
     JSSLOT_DEBUGFRAME_ARGUMENTS,
     JSSLOT_DEBUGFRAME_COUNT
 };
 
+extern Class DebugArguments_class;
+
+enum {
+    JSSLOT_DEBUGARGUMENTS_FRAME,
+    JSSLOT_DEBUGARGUMENTS_COUNT
+};
+
 extern Class DebugObject_class;
 
 enum {
     JSSLOT_DEBUGOBJECT_OWNER,
     JSSLOT_DEBUGOBJECT_CCW,  // cross-compartment wrapper
     JSSLOT_DEBUGOBJECT_COUNT
 };
 
@@ -182,43 +189,23 @@ Debug::fromChildJSObject(JSObject *obj)
 }
 
 bool
 Debug::getScriptFrame(JSContext *cx, StackFrame *fp, Value *vp)
 {
     JS_ASSERT(fp->isScriptFrame());
     FrameMap::AddPtr p = frames.lookupForAdd(fp);
     if (!p) {
-        // Create script Debug.Frame. First copy the arguments.
-        JSObject *argsobj;
-        if (fp->hasArgs()) {
-            uintN argc = fp->numActualArgs();
-            JS_ASSERT(uint(argc) == argc);
-            argsobj = NewDenseAllocatedArray(cx, uint(argc), NULL);
-            if (!argsobj)
-                return false;
-            Value *argv = fp->actualArgs();
-            for (uintN i = 0; i < argc; i++) {
-                Value v = argv[i];
-                if (!wrapDebuggeeValue(cx, &v))
-                    return false;
-                argsobj->setDenseArrayElement(i, v);
-            }
-        } else {
-            argsobj = NULL;
-        }
-
-        // Now create and populate the Debug.Frame object.
+        // Create and populate the Debug.Frame object.
         JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject();
         JSObject *frameobj = NewNonFunction<WithProto::Given>(cx, &DebugFrame_class, proto, NULL);
         if (!frameobj || !frameobj->ensureClassReservedSlots(cx))
             return false;
         frameobj->setPrivate(fp);
         frameobj->setReservedSlot(JSSLOT_DEBUGFRAME_OWNER, ObjectValue(*object));
-        frameobj->setReservedSlot(JSSLOT_DEBUGFRAME_ARGUMENTS, ObjectOrNullValue(argsobj));
 
         if (!frames.add(p, fp, frameobj)) {
             js_ReportOutOfMemory(cx);
             return false;
         }
     }
     vp->setObject(*p->value);
     return true;
@@ -1056,17 +1043,17 @@ JSFunctionSpec Debug::methods[] = {
     JS_FS_END
 };
 
 // === Debug.Frame
 
 Class DebugFrame_class = {
     "Frame", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGFRAME_COUNT),
     PropertyStub, PropertyStub, PropertyStub, StrictPropertyStub,
-    EnumerateStub, ResolveStub, ConvertStub, FinalizeStub,
+    EnumerateStub, ResolveStub, ConvertStub
 };
 
 static JSObject *
 CheckThisFrame(JSContext *cx, Value *vp, const char *fnname, bool checkLive)
 {
     if (!vp[1].isObject()) {
         ReportObjectRequired(cx);
         return NULL;
@@ -1164,22 +1151,106 @@ DebugFrame_getOlder(JSContext *cx, uintN
     for (StackFrame *fp = thisfp->prev(); fp; fp = fp->prev()) {
         if (!fp->isDummyFrame() && dbg->observesFrame(fp))
             return dbg->getScriptFrame(cx, fp, vp);
     }
     vp->setNull();
     return true;
 }
 
+Class DebugArguments_class = {
+    "Arguments", JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGARGUMENTS_COUNT),
+    PropertyStub, PropertyStub, PropertyStub, StrictPropertyStub,
+    EnumerateStub, ResolveStub, ConvertStub
+};
+
+// The getter used for each element of frame.arguments. See DebugFrame_getArguments.
+JSBool
+DebugArguments_getArg(JSContext *cx, uintN argc, Value *vp)
+{
+    JSObject *callee = &CallArgsFromVp(argc, vp).callee();
+    int32 i = callee->getReservedSlot(0).toInt32();
+
+    // Check that the this value is an Arguments object.
+    if (!vp[1].isObject()) {
+        ReportObjectRequired(cx);
+        return false;
+    }
+    JSObject *argsobj = &vp[1].toObject();
+    if (argsobj->getClass() != &DebugArguments_class) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
+                             "Arguments", "getArgument", argsobj->getClass()->name);
+        return false;
+    }
+
+    // Put the Debug.Frame into the this-value slot, then use THIS_FRAME
+    // to check that it is still live and get the fp.
+    vp[1] = argsobj->getReservedSlot(JSSLOT_DEBUGARGUMENTS_FRAME);
+    THIS_FRAME(cx, vp, "get argument", thisobj, fp);
+
+    // Since getters can be extracted and applied to other objects,
+    // there is no guarantee this object has an ith argument.
+    JS_ASSERT(i >= 0);
+    if (uintN(i) < fp->numActualArgs())
+        *vp = fp->actualArgs()[i];
+    else
+        vp->setUndefined();
+    return Debug::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, vp);
+}
+
 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);
+    Value argumentsv = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ARGUMENTS);
+    if (!argumentsv.isUndefined()) {
+        JS_ASSERT(argumentsv.isObjectOrNull());
+        *vp = argumentsv;
+        return true;
+    }
+
+    JSObject *argsobj;
+    if (fp->hasArgs()) {
+        // Create an arguments object.
+        GlobalObject *global = CallArgsFromVp(argc, vp).callee().getGlobal();
+        JSObject *proto;
+        if (!js_GetClassPrototype(cx, global, JSProto_Array, &proto))
+            return false;
+        argsobj = NewNonFunction<WithProto::Given>(cx, &DebugArguments_class, proto, global);
+        if (!argsobj ||
+            !js_SetReservedSlot(cx, argsobj, JSSLOT_DEBUGARGUMENTS_FRAME, ObjectValue(*thisobj)))
+        {
+            return false;
+        }
+
+        JS_ASSERT(fp->numActualArgs() <= 0x7fffffff);
+        int32 fargc = int32(fp->numActualArgs());
+        if (!DefineNativeProperty(cx, argsobj, ATOM_TO_JSID(cx->runtime->atomState.lengthAtom),
+                                  Int32Value(fargc), NULL, NULL,
+                                  JSPROP_PERMANENT | JSPROP_READONLY, 0, 0))
+        {
+            return false;
+        }
+
+        for (int32 i = 0; i < fargc; i++) {
+            JSObject *getobj = js_NewFunction(cx, NULL, DebugArguments_getArg, 0, 0, global, NULL);
+            if (!getobj ||
+                !js_SetReservedSlot(cx, getobj, 0, Int32Value(i)) ||
+                !DefineNativeProperty(cx, argsobj, INT_TO_JSID(i), UndefinedValue(),
+                                      JS_DATA_TO_FUNC_PTR(PropertyOp, getobj), NULL,
+                                      JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER, 0, 0))
+            {
+                return false;
+            }
+        }
+    } else {
+        argsobj = NULL;
+    }
+    *vp = ObjectOrNullValue(argsobj);
+    thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ARGUMENTS, *vp);
     return true;
 }
 
 static JSBool
 DebugFrame_getLive(JSContext *cx, uintN argc, Value *vp)
 {
     JSObject *thisobj = CheckThisFrame(cx, vp, "get live", false);
     if (!thisobj)