Revision: 24620
Author:   [email protected]
Date:     Wed Oct 15 09:11:32 2014 UTC
Log: 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
[email protected], [email protected], [email protected]

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

Patch from Diego Pino <[email protected]>.
https://code.google.com/p/v8/source/detail?r=24620

Modified:
 /branches/bleeding_edge/src/array.js
 /branches/bleeding_edge/src/collection.js
 /branches/bleeding_edge/src/harmony-array.js
 /branches/bleeding_edge/src/macros.py
 /branches/bleeding_edge/test/mjsunit/array-iteration.js
 /branches/bleeding_edge/test/mjsunit/es6/collections.js
 /branches/bleeding_edge/test/mjsunit/harmony/array-find.js
 /branches/bleeding_edge/test/mjsunit/harmony/array-findindex.js

=======================================
--- /branches/bleeding_edge/src/array.js        Tue Sep 30 15:07:21 2014 UTC
+++ /branches/bleeding_edge/src/array.js        Wed Oct 15 09:11:32 2014 UTC
@@ -1133,10 +1133,11 @@
   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 @@
       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 @@
   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 @@
       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 @@
   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 @@
       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 @@
   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 @@
       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 @@
   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 @@
       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);
=======================================
--- /branches/bleeding_edge/src/collection.js   Tue Aug 19 15:15:41 2014 UTC
+++ /branches/bleeding_edge/src/collection.js   Wed Oct 15 09:11:32 2014 UTC
@@ -105,6 +105,12 @@
   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 @@
   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 @@
   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);
   }
 }

=======================================
--- /branches/bleeding_edge/src/harmony-array.js Tue Aug 19 11:38:38 2014 UTC +++ /branches/bleeding_edge/src/harmony-array.js Wed Oct 15 09:11:32 2014 UTC
@@ -26,16 +26,18 @@
     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 @@
     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;
       }
     }
=======================================
--- /branches/bleeding_edge/src/macros.py       Fri Oct 10 14:59:53 2014 UTC
+++ /branches/bleeding_edge/src/macros.py       Wed Oct 15 09:11:32 2014 UTC
@@ -167,6 +167,7 @@
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
=======================================
--- /branches/bleeding_edge/test/mjsunit/array-iteration.js Fri Nov 22 13:50:39 2013 UTC +++ /branches/bleeding_edge/test/mjsunit/array-iteration.js Wed Oct 15 09:11:32 2014 UTC
@@ -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]);
+
 })();
=======================================
--- /branches/bleeding_edge/test/mjsunit/es6/collections.js Tue Aug 19 15:15:41 2014 UTC +++ /branches/bleeding_edge/test/mjsunit/es6/collections.js Wed Oct 15 09:11:32 2014 UTC
@@ -691,6 +691,33 @@
   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 @@
 })();


+(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};
=======================================
--- /branches/bleeding_edge/test/mjsunit/harmony/array-find.js Fri Nov 22 13:50:39 2013 UTC +++ /branches/bleeding_edge/test/mjsunit/harmony/array-find.js Wed Oct 15 09:11:32 2014 UTC
@@ -237,6 +237,24 @@
     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
=======================================
--- /branches/bleeding_edge/test/mjsunit/harmony/array-findindex.js Fri Nov 22 13:50:39 2013 UTC +++ /branches/bleeding_edge/test/mjsunit/harmony/array-findindex.js Wed Oct 15 09:11:32 2014 UTC
@@ -237,6 +237,24 @@
     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

--
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev
--- You received this message because you are subscribed to the Google Groups "v8-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to