Skip to content

Commit

Permalink
Patch Function#call and Function#apply together, more robust than…
Browse files Browse the repository at this point in the history
… single fix.

Ref: es-shims#304

Changed some code back as mentioned in comments.

Changed more code as per comments

Changed more code as per comments.

Changed some variable names to better reflect comments.

Fixed missed invocation.

Added some comments and code changes as discussed.

[tests] Remove unneeded jshint comment.

[Tests] use the preferred it/skip pattern for this strict mode test.

es-shims#345 (comment)
And some other cleanup

Added `arguments` expectations to tests.

Add tests for `Object#toString` of typed arrays and Symbols, if they exist.

Added note about typed array tests.

Fix `hasToStringTagRegExpBug`

Removed RegExp and Array bug detection as can not test, possible Opera 9.
Fixed missing `force` on `defineProperties` that caused the patch to not be applied on IE<9.
Fixed `Uint8ClampedArray` tests for Opera 11 and IE10 that don't have it.

Removed offending test that was moved to detection, but forgotten.

Avoid all possibilities of `call` calling `call`.

Do not pass `undefined` argument, we know IE<9 has unfixable bug.

Final cleanup (hopeully)

Port over work from `apply` fix

Move code so that it is specific to the fix.

Robustness, move before bind.

Remove `Array#slice` tests.

Add notes about `eval` and `apply` avoidance.
  • Loading branch information
Xotic750 committed Dec 9, 2015
1 parent 8584079 commit 1b9ce95
Show file tree
Hide file tree
Showing 3 changed files with 422 additions and 10 deletions.
145 changes: 139 additions & 6 deletions es5-shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ var array_splice = ArrayPrototype.splice;
var array_push = ArrayPrototype.push;
var array_unshift = ArrayPrototype.unshift;
var array_concat = ArrayPrototype.concat;
var str_split = StringPrototype.split;
var call = FunctionPrototype.call;
var apply = FunctionPrototype.apply;
var max = Math.max;
var min = Math.min;

Expand Down Expand Up @@ -175,11 +177,147 @@ var ES = {
}
};

// Check failure of by-index access of string characters (IE < 9)
// and failure of `0 in boxedString` (Rhino)
var boxedString = $Object('a');
var splitString = boxedString[0] !== 'a' || !(0 in boxedString);

//
// Function
// ========
//

// Tests for inconsistent or buggy `[[Class]]` strings.
/* eslint-disable no-useless-call */
var hasToStringTagBasicBug = to_string.call() !== '[object Undefined]' || to_string.call(null) !== '[object Null]';
/* eslint-enable no-useless-call */
var hasToStringTagLegacyArguments = to_string.call(arguments) !== '[object Arguments]';
var hasToStringTagInconsistency = hasToStringTagBasicBug || hasToStringTagLegacyArguments;
// Others that could be fixed:
// Older ES3 native functions like `alert` return `[object Object]`.
// Inconsistent `[[Class]]` strings for `window` or `global`.

var hasApplyArrayLikeDeficiency = (function () {
var arrayLike = { length: 4, 0: 1, 2: 4, 3: true };
var expectedArray = [1, undefined, 4, true];
var actualArray;
try {
actualArray = (function () {
// `array_slice` is safe to use here, no known issue at present.
return array_slice.apply(arguments);
}.apply(null, arrayLike));
} catch (e) {
if (to_string.call(actualArray) !== '[object Array]' || actualArray.length !== arrayLike.length) {
return true;
}
while (expectedArray.length) {
if (actualArray.pop() !== expectedArray.pop()) {
return true;
}
}
}
return false;
}());

var shouldPatchCallApply = hasToStringTagInconsistency || hasApplyArrayLikeDeficiency;

if (shouldPatchCallApply) {
// Constant. ES3 maximum array length.
var MAX_ARRAY_LENGTH = 4294967295;
// To prevent recursion when `call` and `apply` are patched. Robustness.
call.call = call;
call.apply = apply;
apply.call = call;
apply.apply = apply;
}

if (hasToStringTagLegacyArguments) {
// This function is for use within `call` and `apply` only.
// To avoid any possibility of `call` recursion we use original `hasOwnProperty`.
var isDuckTypeArguments = (function (hasOwnProperty) {
return function (value) {
if (value != null) { // Checks `null` or `undefined`.
if (typeof value === 'object' && call.call(hasOwnProperty, value, 'length')) {
var length = value.length;
if (length > -1 && length % 1 === 0 && length <= MAX_ARRAY_LENGTH) {
return !call.call(hasOwnProperty, value, 'arguments') && call.call(hasOwnProperty, value, 'callee');
}
}
}
return false;
};
}(ObjectPrototype.hasOwnProperty));
}

if (shouldPatchCallApply) {
// For use with `call` and `apply` fixes.
var toStringTag = function (value) {
// Add whatever fixes for getting `[[Class]]` strings here.
if (value === null) {
return '[object Null]';
}
if (typeof value === 'undefined') {
return '[object Undefined]';
}
if (hasToStringTagLegacyArguments && isDuckTypeArguments(value)) {
return '[object Arguments]';
}
// `to_string` is safe to use here, no known issue at present.
return call.call(to_string, value);
};
}

defineProperties(FunctionPrototype, {
// ES-5 15.3.4.3
// http://es5.github.io/#x15.3.4.3
// The apply() method calls a function with a given this value and arguments
// provided as an array (or an array-like object).
apply: function (thisArg) {
var argsArray = arguments[1];
if (arguments.length > 1) {
// IE9 (though fix not needed) has a problem here for some reason!!!
// Pretty much any function here causes error `SCRIPT5007: Object expected`.
var type = typeof argsArray;
if (type !== 'undefined' && type !== 'object' && type !== 'function') {
throw new TypeError('Function.prototype.apply: Arguments list has wrong type');
}
}
// If `this` is `Object#toString`, captured or modified.
if (this === to_string || this === Object.prototype.toString) {
return toStringTag(thisArg);
}
// All other applys.
if (arguments.length > 1) {
// Boxed string access bug fix.
if (splitString && to_string.call(thisArg) === '[object String]') {
// `str_split` is safe to use here, no known issue at present.
argsArray = call.call(str_split, argsArray, '');
}
// `array_slice` is safe to use here, no known issue at present.
argsArray = call.call(array_slice, argsArray);
} else {
// `argsArray` was `undefined` (not present).
argsArray = [];
}

return apply.call(this, thisArg, argsArray);
},

// ES-5 15.3.4.4
// http://es5.github.io/#x15.3.4.4
// The call() method calls a function with a given this value and arguments
// provided individually.
call: function (thisArg) {
// If `this` is `Object#toString`, captured or modified.
if (this === to_string || this === Object.prototype.toString) {
return toStringTag(thisArg);
}
// All other calls.
// `array_slice` is safe to use here, no known issue at present.
return apply.call(this, thisArg, call.call(array_slice, arguments, 1));
}
}, shouldPatchCallApply);

// ES-5 15.3.4.5
// http://es5.github.com/#x15.3.4.5

Expand Down Expand Up @@ -320,7 +458,7 @@ defineProperties(FunctionPrototype, {
});

// _Please note: Shortcuts are defined after `Function.prototype.bind` as we
// us it in defining shortcuts.
// use it in defining shortcuts.
var owns = call.bind(ObjectPrototype.hasOwnProperty);
var toStr = call.bind(ObjectPrototype.toString);
var strSlice = call.bind(StringPrototype.slice);
Expand Down Expand Up @@ -371,11 +509,6 @@ defineProperties($Array, { isArray: isArray });
// http://es5.github.com/#x15.4.4.18
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/forEach

// Check failure of by-index access of string characters (IE < 9)
// and failure of `0 in boxedString` (Rhino)
var boxedString = $Object('a');
var splitString = boxedString[0] !== 'a' || !(0 in boxedString);

var properlyBoxesContext = function properlyBoxed(method) {
// Check node 0.6.21 bug where third parameter is not boxed
var properlyBoxesNonStrict = true;
Expand Down
Loading

0 comments on commit 1b9ce95

Please sign in to comment.