diff --git a/src/array.js b/src/array.js index bef7578309..b6f3a89d69 100644 --- a/src/array.js +++ b/src/array.js @@ -1133,10 +1133,11 @@ function ArrayFilter(f, receiver) { if (!IS_SPEC_FUNCTION(f)) { throw MakeTypeError('called_non_callable', [ f ]); } + var needs_wrapper = false; if (IS_NULL_OR_UNDEFINED(receiver)) { receiver = %GetDefaultReceiver(f) || receiver; - } else if (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(f)) { - receiver = ToObject(receiver); + } else { + needs_wrapper = SHOULD_CREATE_WRAPPER(f, receiver); } var result = new $Array(); @@ -1148,7 +1149,8 @@ function ArrayFilter(f, receiver) { var element = array[i]; // Prepare break slots for debugger step in. if (stepping) %DebugPrepareStepInIfStepping(f); - if (%_CallFunction(receiver, element, i, array, f)) { + var new_receiver = needs_wrapper ? ToObject(receiver) : receiver; + if (%_CallFunction(new_receiver, element, i, array, f)) { accumulator[accumulator_length++] = element; } } @@ -1169,10 +1171,11 @@ function ArrayForEach(f, receiver) { if (!IS_SPEC_FUNCTION(f)) { throw MakeTypeError('called_non_callable', [ f ]); } + var needs_wrapper = false; if (IS_NULL_OR_UNDEFINED(receiver)) { receiver = %GetDefaultReceiver(f) || receiver; - } else if (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(f)) { - receiver = ToObject(receiver); + } else { + needs_wrapper = SHOULD_CREATE_WRAPPER(f, receiver); } var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f); @@ -1181,7 +1184,8 @@ function ArrayForEach(f, receiver) { var element = array[i]; // Prepare break slots for debugger step in. if (stepping) %DebugPrepareStepInIfStepping(f); - %_CallFunction(receiver, element, i, array, f); + var new_receiver = needs_wrapper ? ToObject(receiver) : receiver; + %_CallFunction(new_receiver, element, i, array, f); } } } @@ -1200,10 +1204,11 @@ function ArraySome(f, receiver) { if (!IS_SPEC_FUNCTION(f)) { throw MakeTypeError('called_non_callable', [ f ]); } + var needs_wrapper = false; if (IS_NULL_OR_UNDEFINED(receiver)) { receiver = %GetDefaultReceiver(f) || receiver; - } else if (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(f)) { - receiver = ToObject(receiver); + } else { + needs_wrapper = SHOULD_CREATE_WRAPPER(f, receiver); } var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f); @@ -1212,7 +1217,8 @@ function ArraySome(f, receiver) { var element = array[i]; // Prepare break slots for debugger step in. if (stepping) %DebugPrepareStepInIfStepping(f); - if (%_CallFunction(receiver, element, i, array, f)) return true; + var new_receiver = needs_wrapper ? ToObject(receiver) : receiver; + if (%_CallFunction(new_receiver, element, i, array, f)) return true; } } return false; @@ -1230,10 +1236,11 @@ function ArrayEvery(f, receiver) { if (!IS_SPEC_FUNCTION(f)) { throw MakeTypeError('called_non_callable', [ f ]); } + var needs_wrapper = false; if (IS_NULL_OR_UNDEFINED(receiver)) { receiver = %GetDefaultReceiver(f) || receiver; - } else if (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(f)) { - receiver = ToObject(receiver); + } else { + needs_wrapper = SHOULD_CREATE_WRAPPER(f, receiver); } var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f); @@ -1242,7 +1249,8 @@ function ArrayEvery(f, receiver) { var element = array[i]; // Prepare break slots for debugger step in. if (stepping) %DebugPrepareStepInIfStepping(f); - if (!%_CallFunction(receiver, element, i, array, f)) return false; + var new_receiver = needs_wrapper ? ToObject(receiver) : receiver; + if (!%_CallFunction(new_receiver, element, i, array, f)) return false; } } return true; @@ -1259,10 +1267,11 @@ function ArrayMap(f, receiver) { if (!IS_SPEC_FUNCTION(f)) { throw MakeTypeError('called_non_callable', [ f ]); } + var needs_wrapper = false; if (IS_NULL_OR_UNDEFINED(receiver)) { receiver = %GetDefaultReceiver(f) || receiver; - } else if (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(f)) { - receiver = ToObject(receiver); + } else { + needs_wrapper = SHOULD_CREATE_WRAPPER(f, receiver); } var result = new $Array(); @@ -1273,7 +1282,8 @@ function ArrayMap(f, receiver) { var element = array[i]; // Prepare break slots for debugger step in. if (stepping) %DebugPrepareStepInIfStepping(f); - accumulator[i] = %_CallFunction(receiver, element, i, array, f); + var new_receiver = needs_wrapper ? ToObject(receiver) : receiver; + accumulator[i] = %_CallFunction(new_receiver, element, i, array, f); } } %MoveArrayContents(accumulator, result); diff --git a/src/collection.js b/src/collection.js index 0027bd7320..7d185d9c34 100644 --- a/src/collection.js +++ b/src/collection.js @@ -105,6 +105,12 @@ function SetForEach(f, receiver) { if (!IS_SPEC_FUNCTION(f)) { throw MakeTypeError('called_non_callable', [f]); } + var needs_wrapper = false; + if (IS_NULL_OR_UNDEFINED(receiver)) { + receiver = %GetDefaultReceiver(f) || receiver; + } else { + needs_wrapper = SHOULD_CREATE_WRAPPER(f, receiver); + } var iterator = new SetIterator(this, ITERATOR_KIND_VALUES); var key; @@ -113,7 +119,8 @@ function SetForEach(f, receiver) { while (%SetIteratorNext(iterator, value_array)) { if (stepping) %DebugPrepareStepInIfStepping(f); key = value_array[0]; - %_CallFunction(receiver, key, key, this, f); + var new_receiver = needs_wrapper ? ToObject(receiver) : receiver; + %_CallFunction(new_receiver, key, key, this, f); } } @@ -249,13 +256,20 @@ function MapForEach(f, receiver) { if (!IS_SPEC_FUNCTION(f)) { throw MakeTypeError('called_non_callable', [f]); } + var needs_wrapper = false; + if (IS_NULL_OR_UNDEFINED(receiver)) { + receiver = %GetDefaultReceiver(f) || receiver; + } else { + needs_wrapper = SHOULD_CREATE_WRAPPER(f, receiver); + } var iterator = new MapIterator(this, ITERATOR_KIND_ENTRIES); var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f); var value_array = [UNDEFINED, UNDEFINED]; while (%MapIteratorNext(iterator, value_array)) { if (stepping) %DebugPrepareStepInIfStepping(f); - %_CallFunction(receiver, value_array[1], value_array[0], this, f); + var new_receiver = needs_wrapper ? ToObject(receiver) : receiver; + %_CallFunction(new_receiver, value_array[1], value_array[0], this, f); } } diff --git a/src/harmony-array.js b/src/harmony-array.js index 88b878f0a7..06fada7581 100644 --- a/src/harmony-array.js +++ b/src/harmony-array.js @@ -26,16 +26,18 @@ function ArrayFind(predicate /* thisArg */) { // length == 1 thisArg = %_Arguments(1); } + var needs_wrapper = false; if (IS_NULL_OR_UNDEFINED(thisArg)) { thisArg = %GetDefaultReceiver(predicate) || thisArg; - } else if (!IS_SPEC_OBJECT(thisArg) && %IsSloppyModeFunction(predicate)) { - thisArg = ToObject(thisArg); + } else { + needs_wrapper = SHOULD_CREATE_WRAPPER(predicate, thisArg); } for (var i = 0; i < length; i++) { if (i in array) { var element = array[i]; - if (%_CallFunction(thisArg, element, i, array, predicate)) { + var newThisArg = needs_wrapper ? ToObject(thisArg) : thisArg; + if (%_CallFunction(newThisArg, element, i, array, predicate)) { return element; } } @@ -61,16 +63,18 @@ function ArrayFindIndex(predicate /* thisArg */) { // length == 1 thisArg = %_Arguments(1); } + var needs_wrapper = false; if (IS_NULL_OR_UNDEFINED(thisArg)) { thisArg = %GetDefaultReceiver(predicate) || thisArg; - } else if (!IS_SPEC_OBJECT(thisArg) && %IsSloppyModeFunction(predicate)) { - thisArg = ToObject(thisArg); + } else { + needs_wrapper = SHOULD_CREATE_WRAPPER(predicate, thisArg); } for (var i = 0; i < length; i++) { if (i in array) { var element = array[i]; - if (%_CallFunction(thisArg, element, i, array, predicate)) { + var newThisArg = needs_wrapper ? ToObject(thisArg) : thisArg; + if (%_CallFunction(newThisArg, element, i, array, predicate)) { return i; } } diff --git a/src/macros.py b/src/macros.py index ce9d7e8f4b..d8741f78f9 100644 --- a/src/macros.py +++ b/src/macros.py @@ -167,6 +167,7 @@ macro TO_NUMBER_INLINE(arg) = (IS_NUMBER(%IS_VAR(arg)) ? arg : NonNumberToNumber macro TO_OBJECT_INLINE(arg) = (IS_SPEC_OBJECT(%IS_VAR(arg)) ? arg : ToObject(arg)); macro JSON_NUMBER_TO_STRING(arg) = ((%_IsSmi(%IS_VAR(arg)) || arg - arg == 0) ? %_NumberToString(arg) : "null"); macro HAS_OWN_PROPERTY(obj, index) = (%_CallFunction(obj, index, ObjectHasOwnProperty)); +macro SHOULD_CREATE_WRAPPER(functionName, receiver) = (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(functionName)); # Private names. # GET_PRIVATE should only be used if the property is known to exists on obj diff --git a/test/mjsunit/array-iteration.js b/test/mjsunit/array-iteration.js index d11f984bee..13240740fc 100644 --- a/test/mjsunit/array-iteration.js +++ b/test/mjsunit/array-iteration.js @@ -68,6 +68,23 @@ assertEquals(3, count); for (var i in a) assertEquals(2, a[i]); + // Create a new object in each function call when receiver is a + // primitive value. See ECMA-262, Annex C. + a = []; + [1, 2].filter(function() { a.push(this) }, ""); + assertTrue(a[0] !== a[1]); + + // Do not create a new object otherwise. + a = []; + [1, 2].filter(function() { a.push(this) }, {}); + assertEquals(a[0], a[1]); + + // In strict mode primitive values should not be coerced to an object. + a = []; + [1, 2].filter(function() { 'use strict'; a.push(this); }, ""); + assertEquals("", a[0]); + assertEquals(a[0], a[1]); + })(); @@ -109,6 +126,23 @@ a.forEach(function(n) { count++; }); assertEquals(1, count); + // Create a new object in each function call when receiver is a + // primitive value. See ECMA-262, Annex C. + a = []; + [1, 2].forEach(function() { a.push(this) }, ""); + assertTrue(a[0] !== a[1]); + + // Do not create a new object otherwise. + a = []; + [1, 2].forEach(function() { a.push(this) }, {}); + assertEquals(a[0], a[1]); + + // In strict mode primitive values should not be coerced to an object. + a = []; + [1, 2].forEach(function() { 'use strict'; a.push(this); }, ""); + assertEquals("", a[0]); + assertEquals(a[0], a[1]); + })(); @@ -149,6 +183,23 @@ assertTrue(a.every(function(n) { count++; return n == 2; })); assertEquals(2, count); + // Create a new object in each function call when receiver is a + // primitive value. See ECMA-262, Annex C. + a = []; + [1, 2].every(function() { a.push(this); return true; }, ""); + assertTrue(a[0] !== a[1]); + + // Do not create a new object otherwise. + a = []; + [1, 2].every(function() { a.push(this); return true; }, {}); + assertEquals(a[0], a[1]); + + // In strict mode primitive values should not be coerced to an object. + a = []; + [1, 2].every(function() { 'use strict'; a.push(this); return true; }, ""); + assertEquals("", a[0]); + assertEquals(a[0], a[1]); + })(); // @@ -186,6 +237,23 @@ a = a.map(function(n) { return 2*n; }); for (var i in a) assertEquals(4, a[i]); + // Create a new object in each function call when receiver is a + // primitive value. See ECMA-262, Annex C. + a = []; + [1, 2].map(function() { a.push(this) }, ""); + assertTrue(a[0] !== a[1]); + + // Do not create a new object otherwise. + a = []; + [1, 2].map(function() { a.push(this) }, {}); + assertEquals(a[0], a[1]); + + // In strict mode primitive values should not be coerced to an object. + a = []; + [1, 2].map(function() { 'use strict'; a.push(this); }, ""); + assertEquals("", a[0]); + assertEquals(a[0], a[1]); + })(); // @@ -224,4 +292,21 @@ assertTrue(a.some(function(n) { count++; return n == 2; })); assertEquals(2, count); + // Create a new object in each function call when receiver is a + // primitive value. See ECMA-262, Annex C. + a = []; + [1, 2].some(function() { a.push(this) }, ""); + assertTrue(a[0] !== a[1]); + + // Do not create a new object otherwise. + a = []; + [1, 2].some(function() { a.push(this) }, {}); + assertEquals(a[0], a[1]); + + // In strict mode primitive values should not be coerced to an object. + a = []; + [1, 2].some(function() { 'use strict'; a.push(this); }, ""); + assertEquals("", a[0]); + assertEquals(a[0], a[1]); + })(); diff --git a/test/mjsunit/es6/collections.js b/test/mjsunit/es6/collections.js index 940c0b9d1f..94b2aea12b 100644 --- a/test/mjsunit/es6/collections.js +++ b/test/mjsunit/es6/collections.js @@ -691,6 +691,33 @@ for (var i = 9; i >= 0; i--) { assertEquals(4950, accumulated); })(); + +(function TestSetForEachReceiverAsObject() { + var set = new Set(["1", "2"]); + + // Create a new object in each function call when receiver is a + // primitive value. See ECMA-262, Annex C. + var a = []; + set.forEach(function() { a.push(this) }, ""); + assertTrue(a[0] !== a[1]); + + // Do not create a new object otherwise. + a = []; + set.forEach(function() { a.push(this); }, {}); + assertEquals(a[0], a[1]); +})(); + + +(function TestSetForEachReceiverAsObjectInStrictMode() { + var set = new Set(["1", "2"]); + + // In strict mode primitive values should not be coerced to an object. + var a = []; + set.forEach(function() { 'use strict'; a.push(this); }, ""); + assertTrue(a[0] === "" && a[0] === a[1]); +})(); + + (function TestMapForEachInvalidTypes() { assertThrows(function() { Map.prototype.map.forEach.call({}); @@ -998,6 +1025,36 @@ for (var i = 9; i >= 0; i--) { })(); +(function TestMapForEachReceiverAsObject() { + var map = new Map(); + map.set("key1", "value1"); + map.set("key2", "value2"); + + // Create a new object in each function call when receiver is a + // primitive value. See ECMA-262, Annex C. + var a = []; + map.forEach(function() { a.push(this) }, ""); + assertTrue(a[0] !== a[1]); + + // Do not create a new object otherwise. + a = []; + map.forEach(function() { a.push(this); }, {}); + assertEquals(a[0], a[1]); +})(); + + +(function TestMapForEachReceiverAsObjectInStrictMode() { + var map = new Map(); + map.set("key1", "value1"); + map.set("key2", "value2"); + + // In strict mode primitive values should not be coerced to an object. + var a = []; + map.forEach(function() { 'use strict'; a.push(this); }, ""); + assertTrue(a[0] === "" && a[0] === a[1]); +})(); + + // Allows testing iterator-based constructors easily. var oneAndTwo = new Map(); var k0 = {key: 0}; diff --git a/test/mjsunit/harmony/array-find.js b/test/mjsunit/harmony/array-find.js index 9f5750eca0..eb32082277 100644 --- a/test/mjsunit/harmony/array-find.js +++ b/test/mjsunit/harmony/array-find.js @@ -237,6 +237,24 @@ assertEquals(22, a.find(function(val) { return 22 === val; }), undefined); return this.elementAt(key) === val; }, thisArg); assertEquals("b", found); + + // Create a new object in each function call when receiver is a + // primitive value. See ECMA-262, Annex C. + a = []; + [1, 2].find(function() { a.push(this) }, ""); + assertTrue(a[0] !== a[1]); + + // Do not create a new object otherwise. + a = []; + [1, 2].find(function() { a.push(this) }, {}); + assertEquals(a[0], a[1]); + + // In strict mode primitive values should not be coerced to an object. + a = []; + [1, 2].find(function() { 'use strict'; a.push(this); }, ""); + assertEquals("", a[0]); + assertEquals(a[0], a[1]); + })(); // Test exceptions diff --git a/test/mjsunit/harmony/array-findindex.js b/test/mjsunit/harmony/array-findindex.js index a33849dab3..a5df05a05c 100644 --- a/test/mjsunit/harmony/array-findindex.js +++ b/test/mjsunit/harmony/array-findindex.js @@ -237,6 +237,24 @@ assertEquals(3, a.findIndex(function(val) { return 24 === val; })); return this.elementAt(key) === val; }, thisArg); assertEquals(1, index); + + // Create a new object in each function call when receiver is a + // primitive value. See ECMA-262, Annex C. + a = []; + [1, 2].findIndex(function() { a.push(this) }, ""); + assertTrue(a[0] !== a[1]); + + // Do not create a new object otherwise. + a = []; + [1, 2].findIndex(function() { a.push(this) }, {}); + assertEquals(a[0], a[1]); + + // In strict mode primitive values should not be coerced to an object. + a = []; + [1, 2].findIndex(function() { 'use strict'; a.push(this); }, ""); + assertEquals("", a[0]); + assertEquals(a[0], a[1]); + })(); // Test exceptions