Array.prototype.{every, filter, find, findIndex, forEach, map, some}: Use fresh primitive wrapper for calls.

When the receiver is a primitive value, it's cast to an Object before entering the loop. Instead, it should be cast to an Object for each function call while in the loop.

BUG=v8:3536
LOG=Y
R=arv@chromium.org, svenpanne@chromium.org, wingo@igalia.com

Review URL: https://codereview.chromium.org/553413002

Patch from Diego Pino <dpino@igalia.com>.

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@24620 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
svenpanne@chromium.org 2014-10-15 09:11:32 +00:00
parent 985db1a3c8
commit e216ab1d40
8 changed files with 230 additions and 23 deletions

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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]);
})();

View File

@ -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};

View File

@ -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

View File

@ -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