Bug 1314148: Correctly handle wrapped typed arrays in TypedArray.prototype.set. r=lth, waldo
authorAndré Bargull <andre.bargull@gmail.com>
Thu, 02 Mar 2017 01:53:38 -0800
changeset 374654 9b990c5890a81b46e4ffd8ae28c230effe9fba40
parent 374653 c92fa71c097e3edb4a307aa5f2c29d51da501c1d
child 374655 2cd83ad751203f5f91b1a1411fb930be9bff18c1
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslth, waldo
bugs1314148
milestone54.0a1
Bug 1314148: Correctly handle wrapped typed arrays in TypedArray.prototype.set. r=lth, waldo
js/src/tests/ecma_6/TypedArray/set-wrapped.js
js/src/vm/TypedArrayObject-inl.h
js/src/vm/TypedArrayObject.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/set-wrapped.js
@@ -0,0 +1,81 @@
+// Test %TypedArray%.prototype.set(typedArray, offset) when called with wrapped
+// typed array.
+
+if (typeof newGlobal === "function") {
+    var otherGlobal = newGlobal();
+
+    function taintLengthProperty(obj) {
+        Object.defineProperty(obj, "length", {
+            get() {
+                assertEq(true, false);
+            }
+        });
+    }
+
+    for (var TA of anyTypedArrayConstructors) {
+        var target = new TA(4);
+        var source = new otherGlobal[TA.name]([10, 20]);
+
+        // Ensure "length" getter accessor isn't called.
+        taintLengthProperty(source);
+
+        assertEqArray(target, [0, 0, 0, 0]);
+        target.set(source, 1);
+        assertEqArray(target, [0, 10, 20, 0]);
+    }
+
+    // Detachment checks are also applied correctly for wrapped typed arrays.
+    if (typeof detachArrayBuffer === "function") {
+        // Create typed array from different global (explicit constructor call).
+        for (var TA of typedArrayConstructors) {
+            var target = new TA(4);
+            var source = new otherGlobal[TA.name](1);
+            taintLengthProperty(source);
+
+            // Called with wrapped typed array, array buffer already detached.
+            otherGlobal.detachArrayBuffer(source.buffer);
+            assertThrowsInstanceOf(() => target.set(source), TypeError);
+
+            var source = new otherGlobal[TA.name](1);
+            taintLengthProperty(source);
+
+            // Called with wrapped typed array, array buffer detached when
+            // processing offset parameter.
+            var offset = {
+                valueOf() {
+                    otherGlobal.detachArrayBuffer(source.buffer);
+                    return 0;
+                }
+            };
+            assertThrowsInstanceOf(() => target.set(source, offset), TypeError);
+        }
+
+        // Create typed array from different global (implictly created when
+        // ArrayBuffer is a CCW).
+        for (var TA of typedArrayConstructors) {
+            var target = new TA(4);
+            var source = new TA(new otherGlobal.ArrayBuffer(1 * TA.BYTES_PER_ELEMENT));
+            taintLengthProperty(source);
+
+            // Called with wrapped typed array, array buffer already detached.
+            otherGlobal.detachArrayBuffer(source.buffer);
+            assertThrowsInstanceOf(() => target.set(source), TypeError);
+
+            var source = new TA(new otherGlobal.ArrayBuffer(1 * TA.BYTES_PER_ELEMENT));
+            taintLengthProperty(source);
+
+            // Called with wrapped typed array, array buffer detached when
+            // processing offset parameter.
+            var offset = {
+                valueOf() {
+                    otherGlobal.detachArrayBuffer(source.buffer);
+                    return 0;
+                }
+            };
+            assertThrowsInstanceOf(() => target.set(source, offset), TypeError);
+        }
+    }
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/vm/TypedArrayObject-inl.h
+++ b/js/src/vm/TypedArrayObject-inl.h
@@ -268,16 +268,19 @@ class ElementSpecific
      * Act as if the assignments occurred from a fresh copy of |source|, in
      * case the two memory ranges overlap.
      */
     static bool
     setFromTypedArray(JSContext* cx,
                       Handle<TypedArrayObject*> target, Handle<TypedArrayObject*> source,
                       uint32_t offset)
     {
+        // WARNING: |source| may be an unwrapped typed array from a different
+        // compartment. Proceed with caution!
+
         MOZ_ASSERT(TypeIDOfType<T>::id == target->type(),
                    "calling wrong setFromTypedArray specialization");
         MOZ_ASSERT(!target->hasDetachedBuffer(), "target isn't detached");
         MOZ_ASSERT(!source->hasDetachedBuffer(), "source isn't detached");
 
         MOZ_ASSERT(offset <= target->length());
         MOZ_ASSERT(source->length() <= target->length() - offset);
 
@@ -477,16 +480,19 @@ class ElementSpecific
 
   private:
     static bool
     setFromOverlappingTypedArray(JSContext* cx,
                                  Handle<TypedArrayObject*> target,
                                  Handle<TypedArrayObject*> source,
                                  uint32_t offset)
     {
+        // WARNING: |source| may be an unwrapped typed array from a different
+        // compartment. Proceed with caution!
+
         MOZ_ASSERT(TypeIDOfType<T>::id == target->type(),
                    "calling wrong setFromTypedArray specialization");
         MOZ_ASSERT(!target->hasDetachedBuffer(), "target isn't detached");
         MOZ_ASSERT(!source->hasDetachedBuffer(), "source isn't detached");
         MOZ_ASSERT(TypedArrayObject::sameBuffer(target, source),
                    "the provided arrays don't actually overlap, so it's "
                    "undesirable to use this method");
 
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -1498,16 +1498,19 @@ TypedArrayObject::protoAccessors[] = {
     JS_PS_END
 };
 
 template<typename T>
 static inline bool
 SetFromTypedArray(JSContext* cx, Handle<TypedArrayObject*> target,
                   Handle<TypedArrayObject*> source, uint32_t offset)
 {
+    // WARNING: |source| may be an unwrapped typed array from a different
+    // compartment. Proceed with caution!
+
     if (target->isSharedMemory() || source->isSharedMemory())
         return ElementSpecific<T, SharedOps>::setFromTypedArray(cx, target, source, offset);
     return ElementSpecific<T, UnsharedOps>::setFromTypedArray(cx, target, source, offset);
 }
 
 template<typename T>
 static inline bool
 SetFromNonTypedArray(JSContext* cx, Handle<TypedArrayObject*> target, HandleObject source,
@@ -1547,67 +1550,82 @@ TypedArrayObject::set_impl(JSContext* cx
     }
 
     // Steps 8-9.
     if (target->hasDetachedBuffer()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
         return false;
     }
 
-    if (args.get(0).isObject() && args[0].toObject().is<TypedArrayObject>()) {
+    // 22.2.3.23.1, step 15. (22.2.3.23.2 only applies if args[0] is a typed
+    // array, so it doesn't make a difference there to apply ToObject here.)
+    RootedObject src(cx, ToObject(cx, args.get(0)));
+    if (!src)
+        return false;
+
+    Rooted<TypedArrayObject*> srcTypedArray(cx);
+    {
+        JSObject* obj = CheckedUnwrap(src);
+        if (!obj) {
+            ReportAccessDenied(cx);
+            return false;
+        }
+
+        if (obj->is<TypedArrayObject>())
+            srcTypedArray = &obj->as<TypedArrayObject>();
+    }
+
+    if (srcTypedArray) {
         // Remaining steps of 22.2.3.23.2.
-        Rooted<TypedArrayObject*> source(cx, &args[0].toObject().as<TypedArrayObject>());
+
+        // WARNING: |srcTypedArray| may be an unwrapped typed array from a
+        // different compartment. Proceed with caution!
 
         // Steps 11-12.
-        if (source->hasDetachedBuffer()) {
+        if (srcTypedArray->hasDetachedBuffer()) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
             return false;
         }
 
         // Step 10 (Reordered).
         uint32_t targetLength = target->length();
 
         // Step 22 (Split into two checks to provide better error messages).
         if (targetOffset > targetLength) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
             return false;
         }
 
         // Step 22 (Cont'd).
         uint32_t offset = uint32_t(targetOffset);
-        if (source->length() > targetLength - offset) {
+        if (srcTypedArray->length() > targetLength - offset) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
             return false;
         }
 
         // Steps 13-21, 23-28.
         switch (target->type()) {
 #define SET_FROM_TYPED_ARRAY(T, N) \
           case Scalar::N: \
-            if (!SetFromTypedArray<T>(cx, target, source, offset)) \
+            if (!SetFromTypedArray<T>(cx, target, srcTypedArray, offset)) \
                 return false; \
             break;
 JS_FOR_EACH_TYPED_ARRAY(SET_FROM_TYPED_ARRAY)
 #undef SET_FROM_TYPED_ARRAY
           default:
             MOZ_CRASH("Unsupported TypedArray type");
         }
     } else {
         // Remaining steps of 22.2.3.23.1.
 
         // Step 10.
         // We can't reorder this step because side-effects in step 16 can
         // detach the underlying array buffer from the typed array.
         uint32_t targetLength = target->length();
 
-        // Step 15.
-        RootedObject src(cx, ToObject(cx, args.get(0)));
-        if (!src)
-            return false;
-
         // Step 16.
         uint32_t srcLength;
         if (!GetLengthProperty(cx, src, &srcLength))
             return false;
 
         // Step 17 (Split into two checks to provide better error messages).
         if (targetOffset > targetLength) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);