Add Debug.Object.prototype.call.
authorJason Orendorff <jorendorff@mozilla.com>
Tue, 24 May 2011 17:12:43 -0500
changeset 74451 fd5ca689f2d6c2d4fd0f56b4aa105a1e0349e7b8
parent 74450 a3369f2c2600822d9325bb767aa804eec7f1985a
child 74452 9a616d98ca8b5f94552f15a18ca95d58abe94f27
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
milestone6.0a1
Add Debug.Object.prototype.call.
js/src/jit-test/tests/debug/Object-apply-01.js
js/src/jit-test/tests/debug/Object-apply-02.js
js/src/jit-test/tests/debug/Object-apply-03.js
js/src/jit-test/tests/debug/Object-apply-04.js
js/src/jsdbg.cpp
--- a/js/src/jit-test/tests/debug/Object-apply-01.js
+++ b/js/src/jit-test/tests/debug/Object-apply-01.js
@@ -1,47 +1,61 @@
 // |jit-test| debug
-// tests calling script functions via Debug.Object.prototype.apply
+// tests calling script functions via Debug.Object.prototype.apply/call
 
 load(libdir + "asserts.js");
 
 var g = newGlobal("new-compartment");
 g.eval("function f() { debugger; }");
 var dbg = new Debug(g);
 dbg.hooks = {debuggerHandler: function () {}};
 
-dbg.hooks.debuggerHandler = function (frame) {
-    var fn = frame.arguments[0];
-    var cv = fn.apply(null, [9, 16]);
-    assertEq(Object.keys(cv).join(","), "return");
-    assertEq(Object.getPrototypeOf(cv), Object.prototype);
-    assertEq(cv.return, 25);
+var hits = 0;
+function test(usingApply) {
+    dbg.hooks.debuggerHandler = function (frame) {
+	var fn = frame.arguments[0];
+	var cv = usingApply ? fn.apply(null, [9, 16]) : fn.call(null, 9, 16);
+	assertEq(Object.keys(cv).join(","), "return");
+	assertEq(Object.getPrototypeOf(cv), Object.prototype);
+	assertEq(cv.return, 25);
 
-    cv = fn.apply(null, ["hello ", "world"]);
-    assertEq(Object.keys(cv).join(","), "return");
-    assertEq(cv.return, "hello world");
+	cv = usingApply ? fn.apply(null, ["hello ", "world"]) : fn.call(null, "hello ", "world");
+	assertEq(Object.keys(cv).join(","), "return");
+	assertEq(cv.return, "hello world");
 
-    // Handle more or less arguments.
-    assertEq(fn.apply(null, [1, 5, 100]).return, 6);
-    assertEq(fn.apply(null, []).return, NaN);
-    assertEq(fn.apply().return, NaN);
+	// Handle more or less arguments.
+	assertEq((usingApply ? fn.apply(null, [1, 5, 100]) : fn.call(null, 1, 5, 100)).return, 6);
+	assertEq((usingApply ? fn.apply(null, []) : fn.call(null)).return, NaN);
+	assertEq((usingApply ? fn.apply() : fn.call()).return, NaN);
 
-    // Throw if a this-value or argument is an object but not a Debug.Object.
-    assertThrowsInstanceOf(function () { fn.apply({}, []); }, TypeError);
-    assertThrowsInstanceOf(function () { fn.apply(null, [{}]); }, TypeError);
-};
-g.eval("f(function (a, b) { return a + b; });");
+	// Throw if a this-value or argument is an object but not a Debug.Object.
+	assertThrowsInstanceOf(function () { usingApply ? fn.apply({}, []) : fn.call({}); },
+			       TypeError);
+	assertThrowsInstanceOf(function () { usingApply ? fn.apply(null, [{}]) : fn.call(null, {}); },
+			       TypeError);
+	hits++;
+    };
+    g.eval("f(function (a, b) { return a + b; });");
 
-// The callee receives the right arguments even if more arguments are provided
-// than the callee's .length.
-dbg.hooks.debuggerHandler = function (frame) {
-    assertEq(frame.arguments[0].apply(null, ['one', 'two']).return, 2);
-};
-g.eval("f(function () { return arguments.length; });");
+    // The callee receives the right arguments even if more arguments are provided
+    // than the callee's .length.
+    dbg.hooks.debuggerHandler = function (frame) {
+	assertEq((usingApply ? frame.arguments[0].apply(null, ['one', 'two'])
+		             : frame.arguments[0].call(null, 'one', 'two')).return,
+		 2);
+	hits++;
+    };
+    g.eval("f(function () { return arguments.length; });");
 
-// Exceptions are reported as {throw:} completion values.
-dbg.hooks.debuggerHandler = function (frame) {
-    var lose = frame.arguments[0];
-    var cv = lose.apply(null, []);
-    assertEq(Object.keys(cv).join(","), "throw");
-    assertEq(cv.throw, frame.callee);
-};
-g.eval("f(function lose() { throw f; });");
+    // Exceptions are reported as {throw:} completion values.
+    dbg.hooks.debuggerHandler = function (frame) {
+	var lose = frame.arguments[0];
+	var cv = usingApply ? lose.apply(null, []) : lose.call(null);
+	assertEq(Object.keys(cv).join(","), "throw");
+	assertEq(cv.throw, frame.callee);
+	hits++;
+    };
+    g.eval("f(function lose() { throw f; });");
+}
+
+test(true);
+test(false);
+assertEq(hits, 6);
--- a/js/src/jit-test/tests/debug/Object-apply-02.js
+++ b/js/src/jit-test/tests/debug/Object-apply-02.js
@@ -1,48 +1,54 @@
 // |jit-test| debug
-// tests calling native functions via Debug.Object.prototype.apply
+// tests calling native functions via Debug.Object.prototype.apply/call
 
 load(libdir + "asserts.js");
 
 var g = newGlobal("new-compartment");
 g.eval("function f() { debugger; }");
 var dbg = new Debug(g);
 dbg.hooks = {debuggerHandler: function () {}};
 
-dbg.hooks.debuggerHandler = function (frame) {
-    var max = frame.arguments[0];
-    var cv = max.apply(null, [9, 16]);
-    assertEq(cv.return, 16);
+function test(usingApply) {
+    dbg.hooks.debuggerHandler = function (frame) {
+	var max = frame.arguments[0];
+	var cv = usingApply ? max.apply(null, [9, 16]) : max.call(null, 9, 16);
+	assertEq(cv.return, 16);
 
-    cv = max.apply();
-    assertEq(cv.return, -1/0);
+	cv = usingApply ? max.apply() : max.call();
+	assertEq(cv.return, -1/0);
 
-    cv = max.apply(null, [2, 5, 3, 8, 1, 9, 4, 6, 7]);
-    assertEq(cv.return, 9);
+	cv = usingApply ? max.apply(null, [2, 5, 3, 8, 1, 9, 4, 6, 7])
+                        : max.call(null, 2, 5, 3, 8, 1, 9, 4, 6, 7);
+	assertEq(cv.return, 9);
 
-    // second argument to apply must be an array
-    assertThrowsInstanceOf(function () { max.apply(null, 12); }, TypeError);
-};
-g.eval("f(Math.max);");
+	// second argument to apply must be an array
+	assertThrowsInstanceOf(function () { max.apply(null, 12); }, TypeError);
+    };
+    g.eval("f(Math.max);");
 
-dbg.hooks.debuggerHandler = function (frame) {
-    var push = frame.arguments[0];
-    var arr = frame.arguments[1];
-    var cv;
+    dbg.hooks.debuggerHandler = function (frame) {
+	var push = frame.arguments[0];
+	var arr = frame.arguments[1];
+	var cv;
 
-    cv = push.apply(arr, [0, 1, 2]);
-    assertEq(cv.return, 3);
+	cv = usingApply ? push.apply(arr, [0, 1, 2]) : push.call(arr, 0, 1, 2);
+	assertEq(cv.return, 3);
 
-    cv = push.apply(arr, [arr]);
-    assertEq(cv.return, 4);
+	cv = usingApply ? push.apply(arr, [arr]) : push.call(arr, arr);
+	assertEq(cv.return, 4);
 
-    cv = push.apply(arr);
-    assertEq(cv.return, 4);
+	cv = usingApply ? push.apply(arr) : push.call(arr);
+	assertEq(cv.return, 4);
 
-    // you can apply Array.prototype.push to a string; it does ToObject on it.
-    cv = push.apply("hello", ["world"]);
-    assertEq(cv.return, 6);
-};
-g.eval("var a = []; f(Array.prototype.push, a);");
-assertEq(g.a.length, 4);
-assertEq(g.a.slice(0, 3).join(","), "0,1,2");
-assertEq(g.a[3], g.a);
+	// you can apply Array.prototype.push to a string; it does ToObject on it.
+	cv = usingApply ? push.apply("hello", ["world"]) : push.call("hello", "world");
+	assertEq(cv.return, 6);
+    };
+    g.eval("var a = []; f(Array.prototype.push, a);");
+    assertEq(g.a.length, 4);
+    assertEq(g.a.slice(0, 3).join(","), "0,1,2");
+    assertEq(g.a[3], g.a);
+}
+
+test(true);
+test(false);
--- a/js/src/jit-test/tests/debug/Object-apply-03.js
+++ b/js/src/jit-test/tests/debug/Object-apply-03.js
@@ -1,17 +1,24 @@
 // |jit-test| debug
-// reentering the debugger several times via debuggerHandler and apply() on a single stack
+// reentering the debugger several times via debuggerHandler and apply/call on a single stack
 
 var g = newGlobal("new-compartment");
 var dbg = Debug(g);
-dbg.hooks = {
-    debuggerHandler: function (frame) {
-        var n = frame.arguments[0];
-        if (n > 1) {
-            var result = frame.callee.apply(null, [n - 1]);
-            result.return *= n;
-            return result;
-        }
-    }
-};
-g.eval("function fac(n) { debugger; return 1; }");
-assertEq(g.fac(5), 5 * 4 * 3 * 2 * 1);
+
+function test(usingApply) {
+    dbg.hooks = {
+	debuggerHandler: function (frame) {
+	    var n = frame.arguments[0];
+	    if (n > 1) {
+		var result = usingApply ? frame.callee.apply(null, [n - 1])
+                                        : frame.callee.call(null, n - 1);
+		result.return *= n;
+		return result;
+	    }
+	}
+    };
+    g.eval("function fac(n) { debugger; return 1; }");
+    assertEq(g.fac(5), 5 * 4 * 3 * 2 * 1);
+}
+
+test(true);
+test(false);
--- a/js/src/jit-test/tests/debug/Object-apply-04.js
+++ b/js/src/jit-test/tests/debug/Object-apply-04.js
@@ -1,17 +1,18 @@
 // |jit-test| debug
-// Debug.Object.prototype.apply works with function proxies
+// Debug.Object.prototype.apply/call works with function proxies
 
 var g = newGlobal('new-compartment');
 g.eval("function f() { debugger; }");
 var dbg = Debug(g);
 var hits = 0;
 dbg.hooks = {
     debuggerHandler: function (frame) {
         var proxy = frame.arguments[0];
         assertEq(proxy.name, undefined);
         assertEq(proxy.apply(null, [33]).return, 34);
+        assertEq(proxy.call(null, 33).return, 34);
         hits++;
     }
 };
 g.eval("f(Proxy.createFunction({}, function (arg) { return arg + 1; }));");
 assertEq(hits, 1);
--- a/js/src/jsdbg.cpp
+++ b/js/src/jsdbg.cpp
@@ -1451,84 +1451,105 @@ DebugObject_getParameterNames(JSContext 
         for (size_t i = 0; i < fun->nargs; i++)
             result->addressOfDenseArrayElement(i)->setUndefined();
     }
 
     vp->setObject(*result);
     return true;
 }
 
+enum ApplyOrCallMode { ApplyMode, CallMode };
+
 static JSBool
-DebugObject_apply(JSContext *cx, uintN argc, Value *vp)
+ApplyOrCall(JSContext *cx, uintN argc, Value *vp, ApplyOrCallMode mode)
 {
     THIS_DEBUGOBJECT_REFERENT(cx, vp, "apply", obj);
     Debug *dbg = Debug::fromChildJSObject(&vp[1].toObject());
 
     // Any JS exceptions thrown must be in the debugger compartment, so do
     // sanity checks and fallible conversions before entering the debuggee.
+    Value calleev = ObjectValue(*obj);
     if (!obj->isCallable()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
                              "Debug.Object", "apply", obj->getClass()->name);
         return false;
     }
 
     // Unwrap Debug.Objects. This happens in the debugger's compartment since
     // that is where any exceptions must be reported.
-    Value calleev = vp[1];
     Value thisv = argc > 0 ? vp[2] : UndefinedValue();
-    AutoValueVector argv(cx);
-    if (!dbg->unwrapDebuggeeValue(cx, &calleev) || !dbg->unwrapDebuggeeValue(cx, &thisv))
+    if (!dbg->unwrapDebuggeeValue(cx, &thisv))
         return false;
-    if (argc >= 2 && !vp[3].isNullOrUndefined()) {
-        if (!vp[3].isObject()) {
-            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_APPLY_ARGS, js_apply_str);
-            return false;
+    uintN callArgc = 0;
+    Value *callArgv = NULL;
+    AutoValueVector argv(cx);
+    if (mode == ApplyMode) {
+        if (argc >= 2 && !vp[3].isNullOrUndefined()) {
+            if (!vp[3].isObject()) {
+                JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_APPLY_ARGS, js_apply_str);
+                return false;
+            }
+            JSObject *argsobj = &vp[3].toObject();
+            if (!js_GetLengthProperty(cx, argsobj, &callArgc))
+                return false;
+            callArgc = uintN(JS_MIN(callArgc, JS_ARGS_LENGTH_MAX));
+            if (!argv.growBy(callArgc) || !GetElements(cx, argsobj, callArgc, argv.begin()))
+                return false;
+            callArgv = argv.begin();
         }
-        JSObject *argsobj = &vp[3].toObject();
-        uintN length;
-        if (!js_GetLengthProperty(cx, argsobj, &length))
+    } else {
+        callArgc = argc > 0 ? uintN(JS_MIN(argc - 1, JS_ARGS_LENGTH_MAX)) : 0;
+        callArgv = vp + 3;
+    }
+    for (uintN i = 0; i < callArgc; i++) {
+        if (!dbg->unwrapDebuggeeValue(cx, &callArgv[i]))
             return false;
-        length = uintN(JS_MIN(length, JS_ARGS_LENGTH_MAX));
-
-        if (!argv.growBy(length) || !GetElements(cx, argsobj, length, argv.begin()))
-            return false;
-        for (uintN i = 0; i < length; i++) {
-            if (!dbg->unwrapDebuggeeValue(cx, &argv[i]))
-                return false;
-        }
     }
 
     // Enter the debuggee compartment and rewrap all input value for that compartment.
     // (Rewrapping always takes place in the destination compartment.)
     AutoCompartment ac(cx, obj);
     if (!ac.enter() || !cx->compartment->wrap(cx, &calleev) || !cx->compartment->wrap(cx, &thisv))
         return false;
-    for (Value *p = argv.begin(); p != argv.end(); ++p) {
-        if (!cx->compartment->wrap(cx, p))
+    for (uintN i = 0; i < callArgc; i++) {
+        if (!cx->compartment->wrap(cx, &callArgv[i]))
             return false;
     }
 
     // Call the function. Use newCompletionValue to return to the debugger
     // compartment and populate *vp.
     Value rval;
-    bool ok = ExternalInvoke(cx, thisv, calleev, argv.length(), argv.begin(), &rval);
+    bool ok = ExternalInvoke(cx, thisv, calleev, callArgc, callArgv, &rval);
     return dbg->newCompletionValue(ac, ok, rval, vp);
 }
 
+static JSBool
+DebugObject_apply(JSContext *cx, uintN argc, Value *vp)
+{
+    return ApplyOrCall(cx, argc, vp, ApplyMode);
+}
+
+static JSBool
+DebugObject_call(JSContext *cx, uintN argc, Value *vp)
+{
+    return ApplyOrCall(cx, argc, vp, CallMode);
+}
+
 static JSPropertySpec DebugObject_properties[] = {
     JS_PSG("proto", DebugObject_getProto, 0),
     JS_PSG("class", DebugObject_getClass, 0),
     JS_PSG("callable", DebugObject_getCallable, 0),
     JS_PSG("name", DebugObject_getName, 0),
     JS_PSG("parameterNames", DebugObject_getParameterNames, 0),
     JS_PS_END
 };
 
 static JSFunctionSpec DebugObject_methods[] = {
     JS_FN("apply", DebugObject_apply, 0, 0),
+    JS_FN("call", DebugObject_call, 0, 0),
     JS_FS_END
 };
 
 
 // === Glue
 
 extern JS_PUBLIC_API(JSBool)
 JS_DefineDebugObject(JSContext *cx, JSObject *obj)