Bug 1406085 - Only consider array indices to be indexed properties. draft
authorOriol Brufau <oriol-bugzilla@hotmail.com>
Thu, 05 Oct 2017 17:03:07 +0200
changeset 675751 06fce0f95f19b43ebb635f7ba293608dca9809a3
parent 675194 c3b7759671deae73e40ebca01d7f23a326a4b8c2
child 734695 2cf6b9d3fad3a3dab71cbee59a44788d0bf8178d
push id83226
push userbmo:oriol-bugzilla@hotmail.com
push dateThu, 05 Oct 2017 20:20:09 +0000
bugs1406085
milestone58.0a1
Bug 1406085 - Only consider array indices to be indexed properties. MozReview-Commit-ID: AEH4BeFunxh
devtools/server/actors/object.js
devtools/server/tests/unit/test_objectgrips-20.js
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -866,23 +866,23 @@ PropertyIteratorActor.prototype = {
 PropertyIteratorActor.prototype.requestTypes = {
   "names": PropertyIteratorActor.prototype.names,
   "slice": PropertyIteratorActor.prototype.slice,
   "all": PropertyIteratorActor.prototype.all,
 };
 
 function enumArrayProperties(objectActor, options) {
   let length = DevToolsUtils.getProperty(objectActor.obj, "length");
-  if (!isSafePositiveInteger(length)) {
+  if (!isUint32(length)) {
     // Pseudo arrays are flagged as ArrayLike if they have
     // subsequent indexed properties without having any length attribute.
     length = 0;
     let names = objectActor.obj.getOwnPropertyNames();
     for (let key of names) {
-      if (!isSafeIndex(key) || key != length++) {
+      if (!isArrayIndex(key) || key != length++) {
         break;
       }
     }
   }
 
   return {
     size: length,
     propertyName(index) {
@@ -903,32 +903,32 @@ function enumObjectProperties(objectActo
     // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
   }
 
   if (options.ignoreNonIndexedProperties || options.ignoreIndexedProperties) {
     let length = DevToolsUtils.getProperty(objectActor.obj, "length");
     let sliceIndex;
 
     const isLengthTrustworthy =
-      isSafePositiveInteger(length)
-      && (length > 0 && isSafeIndex(names[length - 1]))
-      && !isSafeIndex(names[length]);
+      isUint32(length)
+      && (!length || isArrayIndex(names[length - 1]))
+      && !isArrayIndex(names[length]);
 
     if (!isLengthTrustworthy) {
       // The length property may not reflect what the object looks like, let's find
       // where indexed properties end.
 
-      if (!isSafeIndex(names[0])) {
+      if (!isArrayIndex(names[0])) {
         // If the first item is not a number, this means there is no indexed properties
         // in this object.
         sliceIndex = 0;
       } else {
         sliceIndex = names.length;
         while (sliceIndex > 0) {
-          if (isSafeIndex(names[sliceIndex - 1])) {
+          if (isArrayIndex(names[sliceIndex - 1])) {
             break;
           }
           sliceIndex--;
         }
       }
     } else {
       sliceIndex = length;
     }
@@ -2527,38 +2527,40 @@ function isTypedArray(object) {
  *        The debuggee object to test.
  * @return Boolean
  */
 function isArray(object) {
   return isTypedArray(object) || object.class === "Array";
 }
 
 /**
- * Returns true if the parameter is a safe positive integer.
+ * Returns true if the parameter can be stored as a 32-bit unsigned integer.
+ * If so, it will be suitable for use as the length of an array object.
  *
  * @param num Number
  *        The number to test.
  * @return Boolean
  */
-function isSafePositiveInteger(num) {
-  return Number.isSafeInteger(num) && 1 / num > 0;
+function isUint32(num) {
+  return num >>> 0 === num;
 }
 
 /**
  * Returns true if the parameter is suitable to be an array index.
  *
- * @param num Any
+ * @param str String
  * @return Boolean
  */
-function isSafeIndex(str) {
-  // Transform the parameter to a number using the Unary operator.
-  let num = +str;
-  return isSafePositiveInteger(num) &&
-    // Check the string since unary can transform non number (boolean, null, …).
-    num + "" === str;
+function isArrayIndex(str) {
+  // Transform the parameter to a 32-bit unsigned integer.
+  let num = str >>> 0;
+  // Check that the parameter is a canonical Uint32 index.
+  return num + "" === str &&
+    // Array indices cannot attain the maximum Uint32 value.
+    num != -1 >>> 0;
 }
 
 exports.ObjectActor = ObjectActor;
 exports.PropertyIteratorActor = PropertyIteratorActor;
 exports.LongStringActor = LongStringActor;
 exports.createValueGrip = createValueGrip;
 exports.stringIsLong = stringIsLong;
 exports.longStringGrip = longStringGrip;
--- a/devtools/server/tests/unit/test_objectgrips-20.js
+++ b/devtools/server/tests/unit/test_objectgrips-20.js
@@ -88,16 +88,20 @@ async function run_test_with_server(serv
     evaledObject: { length: 0},
     expectedIndexedProperties: [],
     expectedNonIndexedProperties: [["length", 0]],
   }, {
     evaledObject: { 1: 1 },
     expectedIndexedProperties: [["1", 1]],
     expectedNonIndexedProperties: [],
   }, {
+    evaledObject: { a: 1, [2 ** 32 - 2]: 2, [2 ** 32 - 1]: 3 },
+    expectedIndexedProperties: [["4294967294", 2]],
+    expectedNonIndexedProperties: [["a", 1], ["4294967295", 3]],
+  }, {
     evaledObject: `(() => {
       x = [12, 42];
       x.foo = 90;
       return x;
     })()`,
     expectedIndexedProperties: [["0", 12], ["1", 42]],
     expectedNonIndexedProperties: [["length", 2], ["foo", 90]],
   }, {