Bug 575535 - ES5 Function.prototype.{apply,call} do not coerce |this| to object. We've done this since the original strict-this patch landed, but we haven't deliberately tested this aspect of {apply,call} -- do so here. r=jorendorff
authorJeff Walden <jwalden@mit.edu>
Wed, 08 Dec 2010 11:33:05 -0800
changeset 59938 ae8b6e7b0cd1a189a68dce14cd51eb2586bac59d
parent 59937 1c8d8360b90dbf1d4ffbf782c13c7eede72b5199
child 59939 126cd4bf58e0fab65e5361ef9b3749d218db402b
push id17820
push usercleary@mozilla.com
push dateTue, 04 Jan 2011 21:40:57 +0000
treeherdermozilla-central@969691cfe40e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs575535
milestone2.0b8pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 575535 - ES5 Function.prototype.{apply,call} do not coerce |this| to object. We've done this since the original strict-this patch landed, but we haven't deliberately tested this aspect of {apply,call} -- do so here. r=jorendorff
js/src/tests/ecma_5/Function/15.3.4.3-01.js
js/src/tests/ecma_5/Function/function-call.js
js/src/tests/ecma_5/Function/jstests.list
--- a/js/src/tests/ecma_5/Function/15.3.4.3-01.js
+++ b/js/src/tests/ecma_5/Function/15.3.4.3-01.js
@@ -24,16 +24,18 @@ function expectTypeError(fun, msg)
   catch (e)
   {
     assertEq(e instanceof TypeError, true, msg + "; instead threw " + e);
   }
 }
 
 function fun() { }
 
+var global = this;
+
 
 /* Step 1. */
 var nonfuns = [null, 1, -1, 2.5, "[[Call]]", undefined, true, false, {}];
 for (var i = 0, sz = nonfuns.length; i < sz; i++)
 {
   var f = function()
   {
     Function.prototype.apply.apply(nonfuns[i], [1, 2, 3]);
@@ -42,32 +44,85 @@ for (var i = 0, sz = nonfuns.length; i <
     "expected TypeError calling Function.prototype.apply with uncallable this";
   expectTypeError(f, msg);
 }
 
 
 /* Step 2. */
 var thisObj = {};
 
+var currentThis, currentThisBox;
 function funLength()
 {
   assertEq(arguments.length, 0, "should have been called with no arguments");
+  assertEq(this, currentThis, "wrong this");
+}
+function strictFunLength()
+{
+  "use strict";
+  assertEq(arguments.length, 0, "should have been called with no arguments");
+  assertEq(this, currentThis, "wrong this");
 }
 
+currentThis = global;
 funLength.apply();
+funLength.apply(undefined);
+funLength.apply(undefined, undefined);
+funLength.apply(undefined, null);
+
+currentThis = undefined;
+strictFunLength.apply();
+strictFunLength.apply(undefined);
+strictFunLength.apply(undefined, undefined);
+strictFunLength.apply(undefined, null);
+
+currentThis = null;
+strictFunLength.apply(null);
+strictFunLength.apply(null, undefined);
+strictFunLength.apply(null, null);
 
-function funThisLength()
+currentThis = thisObj;
+funLength.apply(thisObj);
+funLength.apply(thisObj, null);
+funLength.apply(thisObj, undefined);
+strictFunLength.apply(thisObj);
+strictFunLength.apply(thisObj, null);
+strictFunLength.apply(thisObj, undefined);
+
+currentThis = 17;
+strictFunLength.apply(17);
+strictFunLength.apply(17, null);
+strictFunLength.apply(17, undefined);
+
+function funThisPrimitive()
 {
-  assertEq(this, thisObj, "should have gotten thisObj as this");
   assertEq(arguments.length, 0, "should have been called with no arguments");
+  assertEq(this instanceof currentThisBox, true,
+           "this not instanceof " + currentThisBox);
+  assertEq(this.valueOf(), currentThis,
+           "wrong this valueOf()");
 }
 
-funThisLength.apply(thisObj);
-funThisLength.apply(thisObj, null);
-funThisLength.apply(thisObj, undefined);
+currentThis = 17;
+currentThisBox = Number;
+funThisPrimitive.apply(17);
+funThisPrimitive.apply(17, undefined);
+funThisPrimitive.apply(17, null);
+
+currentThis = "foopy";
+currentThisBox = String;
+funThisPrimitive.apply("foopy");
+funThisPrimitive.apply("foopy", undefined);
+funThisPrimitive.apply("foopy", null);
+
+currentThis = false;
+currentThisBox = Boolean;
+funThisPrimitive.apply(false);
+funThisPrimitive.apply(false, undefined);
+funThisPrimitive.apply(false, null);
 
 
 /* Step 3. */
 var nonobjs = [1, -1, 2.5, "[[Call]]", true, false];
 for (var i = 0, sz = nonobjs.length; i < sz; i++)
 {
   var f = function() { fun.apply(thisObj, nonobjs[i]); };
   var msg = "should have thrown a TypeError with non-object arguments";
@@ -82,16 +137,21 @@ try
   fun.apply(thisObj, args);
 }
 catch (e)
 {
   assertEq(e, 42, "didn't throw result of [[Get]] on arguments object");
 }
 
 
+/*
+ * NB: There was an erratum removing the steps numbered 5 and 7 in the original
+ *     version of ES5; see also the comments in js_fun_apply.
+ */
+
 /* Step 5. */
 var called = false;
 var argsObjectLength =
   { length: { valueOf: function() { called = true; return 17; } } };
 
 fun.apply({}, argsObjectLength);
 assertEq(called, true, "should have been set in valueOf called via ToUint32");
 
@@ -108,44 +168,78 @@ var argsObjectPrimitiveLength =
         }
       }
   };
 fun.apply({}, argsObjectPrimitiveLength);
 assertEq(upvar, "both", "didn't call all hooks properly");
 
 
 /* Step 6-9. */
-var steps = [];
+var seenThis, res, steps;
 var argsAccessors =
   {
-    length: 3,
+    length: 4,
     get 0() { steps.push("0"); return 1; },
     get 1() { steps.push("1"); return 2; },
-    get 2() { steps.push("2"); return 4; },
+    // make sure values shine through holes
+    get 3() { steps.push("3"); return 8; },
   };
 
-var seenThis = "not seen";
+Object.prototype[2] = 729;
+
+seenThis = "not seen";
 function argsAsArray()
 {
   seenThis = this;
-  steps.push("3");
+  steps.push(Math.PI);
   return Array.prototype.map.call(arguments, function(v) { return v; });
 }
 
-var res = argsAsArray.apply(thisObj, argsAccessors);
+steps = [];
+res = argsAsArray.apply(thisObj, argsAccessors);
 assertEq(seenThis, thisObj, "saw wrong this");
 
 assertEq(steps.length, 4, "wrong steps: " + steps);
-for (var i = 0; i < 3; i++)
-  assertEq(steps[i], "" + i, "bad step " + i);
-assertEq(steps[3], "3", "bad call");
+assertEq(steps[0], "0", "bad step 0");
+assertEq(steps[1], "1", "bad step 1");
+assertEq(steps[2], "3", "bad step 3");
+assertEq(steps[3], Math.PI, "bad last step");
+
+assertEq(res.length, 4, "wrong return: " + res);
+assertEq(res[0], 1, "wrong ret[0]");
+assertEq(res[1], 2, "wrong ret[0]");
+assertEq(res[2], 729, "wrong ret[0]");
+assertEq(res[3], 8, "wrong ret[0]");
 
-assertEq(res.length, 3, "wrong return: " + res);
-for (var i = 0; i < 3; i++)
-  assertEq(res[i], 1 << i, "wrong ret[" + i + "]: " + res[i]);
+seenThis = "not seen";
+function strictArgsAsArray()
+{
+  "use strict";
+  seenThis = this;
+  steps.push(NaN);
+  return Array.prototype.map.call(arguments, function(v) { return v; });
+}
+
+steps = [];
+res = strictArgsAsArray.apply(null, argsAccessors);
+assertEq(seenThis, null, "saw wrong this");
 
+assertEq(steps.length, 4, "wrong steps: " + steps);
+assertEq(steps[0], "0", "bad step 0");
+assertEq(steps[1], "1", "bad step 1");
+assertEq(steps[2], "3", "bad step 3");
+assertEq(steps[3], 0 / 0, "bad last step");
+
+assertEq(res.length, 4, "wrong return: " + res);
+assertEq(res[0], 1, "wrong ret[0]");
+assertEq(res[1], 2, "wrong ret[0]");
+assertEq(res[2], 729, "wrong ret[0]");
+assertEq(res[3], 8, "wrong ret[0]");
+
+strictArgsAsArray.apply(17, argsAccessors);
+assertEq(seenThis, 17, "saw wrong this");
 
 /******************************************************************************/
 
 if (typeof reportCompare === "function")
   reportCompare(true, true);
 
 print("All tests passed!");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_5/Function/function-call.js
@@ -0,0 +1,134 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ * Contributor:
+ *   Jeff Walden <jwalden+code@mit.edu>
+ */
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 575535;
+var summary = 'Function.prototype.call';
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+function expectTypeError(fun, msg)
+{
+  try
+  {
+    fun();
+    assertEq(true, false, "should have thrown a TypeError");
+  }
+  catch (e)
+  {
+    assertEq(e instanceof TypeError, true, msg + "; instead threw " + e);
+  }
+}
+
+function fun() { }
+
+var global = this;
+
+assertEq(Function.prototype.call.length, 1);
+
+
+/* Step 1. */
+var nonfuns = [null, 1, -1, 2.5, "[[Call]]", undefined, true, false, {}];
+for (var i = 0, sz = nonfuns.length; i < sz; i++)
+{
+  var f = function()
+  {
+    Function.prototype.call.apply(nonfuns[i]);
+  };
+  var msg =
+    "expected TypeError calling Function.prototype.call with uncallable this";
+  expectTypeError(f, msg);
+}
+
+
+/* Steps 2-4. */
+function none()
+{
+  assertEq(this, global, "bad this");
+  assertEq(arguments.length, 0, "wrong arguments");
+}
+
+none.call();
+none.call(undefined);
+none.call(null);
+
+var seenThis;
+function strictNone()
+{
+  "use strict";
+  assertEq(this, seenThis, "bad this");
+  assertEq(arguments.length, 0, "wrong arguments");
+}
+
+seenThis = undefined;
+strictNone.call();
+strictNone.call(undefined);
+
+seenThis = null;
+strictNone.call(null);
+
+seenThis = 17;
+strictNone.call(17);
+
+var seenThisBox, args;
+function some()
+{
+  assertEq(this instanceof seenThisBox, true,
+           "this not instanceof " + seenThisBox);
+  assertEq(this.valueOf(), seenThis,
+           "wrong this valueOf()");
+  assertEq(arguments.length, args.length, "wrong arguments count");
+  for (var i = 0; i < args.length; i++)
+    assertEq(arguments[i], args[i], "wrong argument " + i);
+}
+
+seenThis = false;
+seenThisBox = Boolean;
+args = [8, 6, 7, NaN, undefined, 0.3];
+some.call(false, 8, 6, 7, NaN, undefined, 0.3);
+
+var obj = {};
+
+seenThis = "foo";
+seenThisBox = String;
+args = [obj];
+some.call("foo", obj);
+
+seenThis = obj;
+seenThisBox = Object;
+some.call(obj, obj);
+
+function strictSome()
+{
+  "use strict";
+  assertEq(this, seenThis, "wrong this");
+  assertEq(arguments.length, args.length, "wrong arguments count");
+  for (var i = 0; i < args.length; i++)
+    assertEq(arguments[i], args[i], "wrong argument " + i);
+}
+
+seenThis = NaN;
+args = [8, 6, 7, NaN, undefined, 0.3];
+strictSome.call(NaN, 8, 6, 7, NaN, undefined, 0.3);
+
+seenThis = "foo";
+args = [obj];
+strictSome.call("foo", obj);
+
+seenThis = obj;
+strictSome.call(obj, obj);
+
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("All tests passed!");
--- a/js/src/tests/ecma_5/Function/jstests.list
+++ b/js/src/tests/ecma_5/Function/jstests.list
@@ -1,10 +1,11 @@
 url-prefix ../../jsreftest.html?test=ecma_5/Function/
 script 10.2.1.1.6.js
 script 15.3.4.3-01.js
 script arguments-caller-callee.js
 script function-caller.js
 script strict-arguments.js
 script arguments-property-attributes.js
 script function-bind.js
+script function-call.js
 script redefine-arguments-length.js
 script builtin-no-prototype.js