Bug 1413794 - Typed array [[Set]] wrongly inspects the receiver when a canonical numeric string is passed as property name. r=anba
authorTom Schuster <evilpies@gmail.com>
Sun, 01 Jul 2018 13:03:11 +0200
changeset 424767 846e6b6678b6c6804d3d86fd7f86c6ca4d9a477d
parent 424766 5cad39f6116b0f2ab7df5ac0bcb518775f03af97
child 424768 ae97d490d3dd98066fe53cce532ca995ccaac01e
push id34223
push useraiakab@mozilla.com
push dateTue, 03 Jul 2018 08:56:10 +0000
treeherdermozilla-central@a0e47ebc4c06 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersanba
bugs1413794
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1413794 - Typed array [[Set]] wrongly inspects the receiver when a canonical numeric string is passed as property name. r=anba
js/src/jit-test/tests/proxy/testDirectProxySetArray4.js
js/src/tests/non262/TypedArray/set-with-receiver.js
js/src/vm/NativeObject.cpp
--- a/js/src/jit-test/tests/proxy/testDirectProxySetArray4.js
+++ b/js/src/jit-test/tests/proxy/testDirectProxySetArray4.js
@@ -16,9 +16,8 @@ function test(arr) {
     });
     var hits = 0;
     p[0] = "ponies";
     assertEq(hits, 1);
     assertEq(arr[0], 123);
 }
 
 test([123]);
-test(new Int32Array([123]));
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/TypedArray/set-with-receiver.js
@@ -0,0 +1,33 @@
+for (var constructor of anyTypedArrayConstructors) {
+    var receiver = new Proxy({}, {
+        getOwnPropertyDescriptor(p) {
+            throw "fail";
+        },
+
+        defineProperty() {
+            throw "fail";
+        }
+    });
+
+    var ta = new constructor(1);
+    assertEq(Reflect.set(ta, 0, 47, receiver), true);
+    assertEq(ta[0], 47);
+
+    // Out-of-bounds
+    assertEq(Reflect.set(ta, 10, 47, receiver), false);
+    assertEq(ta[10], undefined);
+
+    // Detached
+    if (typeof detachArrayBuffer === "function" &&
+        !isSharedConstructor(constructor))
+    {
+        detachArrayBuffer(ta.buffer)
+
+        assertEq(ta[0], undefined);
+        assertEq(Reflect.set(ta, 0, 47, receiver), false);
+        assertEq(ta[0], undefined);
+    }
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -1914,16 +1914,46 @@ js::NativeDefineDataProperty(JSContext* 
 bool
 js::NativeDefineDataProperty(JSContext* cx, HandleNativeObject obj, PropertyName* name,
                              HandleValue value, unsigned attrs)
 {
     RootedId id(cx, NameToId(name));
     return NativeDefineDataProperty(cx, obj, id, value, attrs);
 }
 
+
+// ES2019 draft rev e7dc63fb5d1c26beada9ffc12dc78aa6548f1fb5
+// 9.4.5.9 IntegerIndexedElementSet
+static bool
+DefineNonexistentTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj, uint64_t index,
+                                   HandleValue v, ObjectOpResult& result)
+{
+    // This method is only called for non-existent properties, which
+    // means any absent indexed property must be out of range.
+    MOZ_ASSERT(index >= obj->length());
+
+    // Steps 1-2 are enforced by the caller.
+
+    // Step 3.
+    // We still need to call ToNumber, because of its possible side
+    // effects.
+    double d;
+    if (!ToNumber(cx, v, &d))
+        return false;
+
+    // Steps 4-5.
+    // ToNumber may have detached the array buffer.
+    if (obj->hasDetachedBuffer())
+        return result.failSoft(JSMSG_TYPED_ARRAY_DETACHED);
+
+    // Steps 6-9.
+    // We (wrongly) ignore out of range defines.
+    return result.failSoft(JSMSG_BAD_INDEX);
+}
+
 static bool
 DefineNonexistentProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
                           HandleValue v, ObjectOpResult& result)
 {
     // Optimized NativeDefineProperty() version for known absent properties.
 
     // Dispense with custom behavior of exotic native objects first.
     if (obj->is<ArrayObject>()) {
@@ -1936,40 +1966,18 @@ DefineNonexistentProperty(JSContext* cx,
         if (IdIsIndex(id, &index)) {
             if (WouldDefinePastNonwritableLength(&obj->as<ArrayObject>(), index))
                 return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
         }
     } else if (obj->is<TypedArrayObject>()) {
         // 9.4.5.5 step 2. Indexed properties of typed arrays are special.
         uint64_t index;
         if (IsTypedArrayIndex(id, &index)) {
-            // ES2019 draft rev e7dc63fb5d1c26beada9ffc12dc78aa6548f1fb5
-            // 9.4.5.9 IntegerIndexedElementSet
-
-            // This method is only called for non-existent properties, which
-            // means any absent indexed property must be out of range.
-            MOZ_ASSERT(index >= obj->as<TypedArrayObject>().length());
-
-            // Steps 1-2 are enforced by the caller.
-
-            // Step 3.
-            // We still need to call ToNumber, because of its possible side
-            // effects.
-            double d;
-            if (!ToNumber(cx, v, &d))
-                return false;
-
-            // Steps 4-5.
-            // ToNumber may have detached the array buffer.
-            if (obj->as<TypedArrayObject>().hasDetachedBuffer())
-                return result.failSoft(JSMSG_TYPED_ARRAY_DETACHED);
-
-            // Steps 6-9.
-            // We (wrongly) ignore out of range defines.
-            return result.failSoft(JSMSG_BAD_INDEX);
+            Rooted<TypedArrayObject*> tobj(cx, &obj->as<TypedArrayObject>());
+            return DefineNonexistentTypedArrayElement(cx, tobj, index, v, result);
         }
     } else if (obj->is<ArgumentsObject>()) {
         // If this method is called with either |length| or |@@iterator|, the
         // property was previously deleted and hence should already be marked
         // as overridden.
         MOZ_ASSERT_IF(id == NameToId(cx->names().length),
                       obj->as<ArgumentsObject>().hasOverriddenLength());
         MOZ_ASSERT_IF(JSID_IS_SYMBOL(id) && JSID_TO_SYMBOL(id) == cx->wellKnownSymbols().iterator,
@@ -2646,58 +2654,68 @@ SetNonexistentProperty(JSContext* cx, Ha
 
             MOZ_ASSERT(!cx->helperThread());
             return op(cx, obj, id, desc, result);
         }
 
         return DefineNonexistentProperty(cx, obj, id, v, result);
     }
 
+    if (IsQualified && obj->is<TypedArrayObject>()) {
+        // 9.4.5.5 step 2. Indexed properties of typed arrays are special.
+        uint64_t index;
+        if (IsTypedArrayIndex(id, &index)) {
+            Rooted<TypedArrayObject*> tobj(cx, &obj->as<TypedArrayObject>());
+            return DefineNonexistentTypedArrayElement(cx, tobj, index, v, result);
+        }
+    }
+
     return SetPropertyByDefining(cx, id, v, receiver, result);
 }
 
-/*
- * Set an existing own property obj[index] that's a dense element or typed
- * array element.
- */
+// ES2019 draft rev e7dc63fb5d1c26beada9ffc12dc78aa6548f1fb5
+// 9.4.5.9 IntegerIndexedElementSet
+// Set an existing own property obj[index] that's a typed array element.
 static bool
-SetDenseOrTypedArrayElement(JSContext* cx, HandleNativeObject obj, uint32_t index, HandleValue v,
-                            ObjectOpResult& result)
+SetTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj, uint32_t index, HandleValue v,
+                     ObjectOpResult& result)
 {
-    if (obj->is<TypedArrayObject>()) {
-        // ES2019 draft rev e7dc63fb5d1c26beada9ffc12dc78aa6548f1fb5
-        // 9.4.5.9 IntegerIndexedElementSet
-
-        // Steps 1-2 are enforced by the caller.
-
-        // Step 3.
-        double d;
-        if (!ToNumber(cx, v, &d))
-            return false;
-
-        // Steps 6-7 don't apply for existing typed array elements.
-
-        // Steps 8-16.
-        // Silently do nothing for out-of-bounds sets, for consistency with
-        // current behavior.  (ES6 currently says to throw for this in
-        // strict mode code, so we may eventually need to change.)
-        uint32_t len = obj->as<TypedArrayObject>().length();
-        if (index < len) {
-            TypedArrayObject::setElement(obj->as<TypedArrayObject>(), index, d);
-            return result.succeed();
-        }
-
-        // Steps 4-5.
-        // A previously existing typed array element can only be out-of-bounds
-        // if the above ToNumber call detached the typed array's buffer.
-        MOZ_ASSERT(obj->as<TypedArrayObject>().hasDetachedBuffer());
-
-        return result.failSoft(JSMSG_TYPED_ARRAY_DETACHED);
+    // Steps 1-2 are enforced by the caller.
+
+    // Step 3.
+    double d;
+    if (!ToNumber(cx, v, &d))
+        return false;
+
+    // Steps 6-7 don't apply for existing typed array elements.
+
+    // Steps 8-16.
+    // Silently do nothing for out-of-bounds sets, for consistency with
+    // current behavior.  (ES6 currently says to throw for this in
+    // strict mode code, so we may eventually need to change.)
+    uint32_t len = obj->as<TypedArrayObject>().length();
+    if (index < len) {
+        TypedArrayObject::setElement(obj->as<TypedArrayObject>(), index, d);
+        return result.succeed();
     }
 
+    // Steps 4-5.
+    // A previously existing typed array element can only be out-of-bounds
+    // if the above ToNumber call detached the typed array's buffer.
+    MOZ_ASSERT(obj->as<TypedArrayObject>().hasDetachedBuffer());
+
+    return result.failSoft(JSMSG_TYPED_ARRAY_DETACHED);
+}
+
+// Set an existing own property obj[index] that's a dense element.
+static bool
+SetDenseElement(JSContext* cx, HandleNativeObject obj, uint32_t index, HandleValue v,
+                ObjectOpResult& result)
+{
+    MOZ_ASSERT(!obj->is<TypedArrayObject>());
     MOZ_ASSERT(obj->containsDenseElement(index));
 
     if (!obj->maybeCopyElementsForWrite(cx))
         return false;
 
     obj->setDenseElementWithType(cx, index, v);
     return result.succeed();
 }
@@ -2711,23 +2729,29 @@ SetDenseOrTypedArrayElement(JSContext* c
  * dense or typed array element (i.e. not actually a pointer to a Shape).
  */
 static bool
 SetExistingProperty(JSContext* cx, HandleId id, HandleValue v, HandleValue receiver,
                     HandleNativeObject pobj, Handle<PropertyResult> prop, ObjectOpResult& result)
 {
     // Step 5 for dense elements.
     if (prop.isDenseOrTypedArrayElement()) {
+        // TypedArray [[Set]] ignores the receiver completely.
+        if (pobj->is<TypedArrayObject>()) {
+            Rooted<TypedArrayObject*> tobj(cx, &pobj->as<TypedArrayObject>());
+            return SetTypedArrayElement(cx, tobj, JSID_TO_INT(id), v, result);
+        }
+
         // Step 5.a.
         if (pobj->denseElementsAreFrozen())
             return result.fail(JSMSG_READ_ONLY);
 
         // Pure optimization for the common case:
         if (receiver.isObject() && pobj == &receiver.toObject())
-            return SetDenseOrTypedArrayElement(cx, pobj, JSID_TO_INT(id), v, result);
+            return SetDenseElement(cx, pobj, JSID_TO_INT(id), v, result);
 
         // Steps 5.b-f.
         return SetPropertyByDefining(cx, id, v, receiver, result);
     }
 
     // Step 5 for all other properties.
     RootedShape shape(cx, prop.shape());
     if (shape->isDataDescriptor()) {