Source code
Revision control
Copy as Markdown
Other Tools
/*
* Any copyright is dedicated to the Public Domain.
* Contributor:
* Jeff Walden <jwalden+code@mit.edu>
*/
//-----------------------------------------------------------------------------
var BUGNUMBER = 562448;
var summary = 'Function.prototype.apply should accept any arraylike arguments';
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;
/* 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]);
};
var msg =
"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);
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(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()");
}
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";
expectTypeError(f, msg);
}
/* Step 4. */
var args = { get length() { throw 42; } };
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");
var upvar = "unset";
var argsObjectPrimitiveLength =
{
length:
{
valueOf: function() { upvar = "valueOf"; return {}; },
toString: function()
{
upvar = upvar === "valueOf" ? "both" : "toString";
return 17;
}
}
};
fun.apply({}, argsObjectPrimitiveLength);
assertEq(upvar, "both", "didn't call all hooks properly");
/* Step 6-9. */
var seenThis, res, steps;
var argsAccessors =
{
length: 4,
get 0() { steps.push("0"); return 1; },
get 1() { steps.push("1"); return 2; },
// make sure values shine through holes
get 3() { steps.push("3"); return 8; },
};
Object.prototype[2] = 729;
seenThis = "not seen";
function argsAsArray()
{
seenThis = this;
steps.push(Math.PI);
return Array.prototype.map.call(arguments, function(v) { return v; });
}
steps = [];
res = argsAsArray.apply(thisObj, argsAccessors);
assertEq(seenThis, thisObj, "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], 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]");
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!");