Bug 1165053 - Part 7: Call SpeciesConstructor in TypedArray ctors. r=lth
☠☠ backed out by 8b4dc7fe5521 ☠ ☠
authorTooru Fujisawa <arai_a@mac.com>
Sat, 05 Mar 2016 18:57:51 +0900
changeset 290601 51249df95c69025fc997ef74a38be9ee7a89dfcd
parent 290600 27189d8e678de79cb4b8e00ec6230cd82442e831
child 290602 77117d1570b1b653d0b6b497f95a5a6a1bc442f2
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslth
bugs1165053
milestone48.0a1
Bug 1165053 - Part 7: Call SpeciesConstructor in TypedArray ctors. r=lth
js/src/tests/ecma_6/TypedArray/Tconstructor-fromTypedArray-byteLength.js
js/src/tests/ecma_6/TypedArray/constructor-ArrayBuffer-species-wrap.js
js/src/tests/ecma_6/TypedArray/constructor-ArrayBuffer-species.js
js/src/vm/ArrayBufferObject-inl.h
js/src/vm/ArrayBufferObject.h
js/src/vm/TypedArrayObject.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/Tconstructor-fromTypedArray-byteLength.js
@@ -0,0 +1,41 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+const sharedConstructors = [];
+
+if (typeof SharedArrayBuffer != "undefined")
+    sharedConstructors.push(sharedConstructor(Int8Array),
+                            sharedConstructor(Uint8Array),
+                            sharedConstructor(Int16Array),
+                            sharedConstructor(Uint16Array),
+                            sharedConstructor(Int32Array),
+                            sharedConstructor(Uint32Array),
+                            sharedConstructor(Float32Array),
+                            sharedConstructor(Float64Array));
+
+var g = newGlobal();
+
+var arr = [1, 2, 3];
+for (var constructor of constructors.concat(sharedConstructors)) {
+    var tarr = new constructor(arr);
+    for (var constructor2 of constructors) {
+        var copied = new constructor2(tarr);
+        assertEq(copied.buffer.byteLength, arr.length * constructor2.BYTES_PER_ELEMENT);
+
+        g.tarr = tarr;
+        copied = g.eval(`new ${constructor2.name}(tarr);`);
+        assertEq(copied.buffer.byteLength, arr.length * constructor2.BYTES_PER_ELEMENT);
+    }
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/constructor-ArrayBuffer-species-wrap.js
@@ -0,0 +1,75 @@
+// |reftest| skip-if(!xulRuntime.shell)
+
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+let g = newGlobal();
+
+// Both TypedArray and ArrayBuffer from different global.
+for (let ctor of constructors) {
+  let a = g.eval(`new ${ctor.name}([1, 2, 3, 4, 5]);`);
+  for (let ctor2 of constructors) {
+    let b = new ctor2(a);
+    assertEq(Object.getPrototypeOf(b).constructor, ctor2);
+    assertEq(Object.getPrototypeOf(b.buffer).constructor, g.ArrayBuffer);
+  }
+}
+
+// Only ArrayBuffer from different global.
+let called = false;
+let origSpecies = Object.getOwnPropertyDescriptor(ArrayBuffer, Symbol.species);
+let modSpecies = {
+  get() {
+    called = true;
+    return g.ArrayBuffer;
+  }
+};
+for (let ctor of constructors) {
+  let a = new ctor([1, 2, 3, 4, 5]);
+  for (let ctor2 of constructors) {
+    called = false;
+    Object.defineProperty(ArrayBuffer, Symbol.species, modSpecies);
+    let b = new ctor2(a);
+    Object.defineProperty(ArrayBuffer, Symbol.species, origSpecies);
+    assertEq(called, true);
+    assertEq(Object.getPrototypeOf(b).constructor, ctor2);
+    assertEq(Object.getPrototypeOf(b.buffer).constructor, g.ArrayBuffer);
+  }
+}
+
+// Only TypedArray from different global.
+g.otherArrayBuffer = ArrayBuffer;
+g.eval(`
+var called = false;
+var origSpecies = Object.getOwnPropertyDescriptor(ArrayBuffer, Symbol.species);
+var modSpecies = {
+  get() {
+    called = true;
+    return otherArrayBuffer;
+  }
+};
+`);
+for (let ctor of constructors) {
+  let a = g.eval(`new ${ctor.name}([1, 2, 3, 4, 5]);`);
+  for (let ctor2 of constructors) {
+    g.called = false;
+    g.eval(`Object.defineProperty(ArrayBuffer, Symbol.species, modSpecies);`);
+    let b = new ctor2(a);
+    g.eval(`Object.defineProperty(ArrayBuffer, Symbol.species, origSpecies);`);
+    assertEq(g.called, true);
+    assertEq(Object.getPrototypeOf(b).constructor, ctor2);
+    assertEq(Object.getPrototypeOf(b.buffer).constructor, ArrayBuffer);
+  }
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/constructor-ArrayBuffer-species.js
@@ -0,0 +1,63 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+let logs = [];
+for (let ctor of constructors) {
+  let arr = new ctor([1, 2, 3, 4, 5, 6, 7, 8]);
+
+  let ctorObj = {};
+
+  let proxyProto = new Proxy({}, {
+    get(that, name) {
+      logs.push("get proto." + String(name));
+      if (name == "constructor")
+        return ctorObj;
+      throw new Error("unexpected prop access");
+    }
+  });
+
+  arr.buffer.constructor = {
+    get [Symbol.species]() {
+      logs.push("get @@species");
+      let C = new Proxy(function(...args) {
+        logs.push("call ctor");
+        return new ArrayBuffer(...args);
+      }, {
+        get(that, name) {
+          logs.push("get ctor." + String(name));
+          if (name == "prototype") {
+            return proxyProto;
+          }
+          throw new Error("unexpected prop access");
+        }
+      });
+      return C;
+    }
+  };
+
+  for (let ctor2 of constructors) {
+    logs.length = 0;
+    let arr2 = new ctor2(arr);
+    assertDeepEq(logs, ["get @@species", "get ctor.prototype"]);
+
+    logs.length = 0;
+    assertEq(Object.getPrototypeOf(arr2.buffer), proxyProto);
+    assertDeepEq(logs, []);
+
+    logs.length = 0;
+    assertEq(arr2.buffer.constructor, ctorObj);
+    assertDeepEq(logs, ["get proto.constructor"]);
+  }
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/vm/ArrayBufferObject-inl.h
+++ b/js/src/vm/ArrayBufferObject-inl.h
@@ -22,16 +22,24 @@ inline SharedMem<uint8_t*>
 ArrayBufferObjectMaybeShared::dataPointerEither()
 {
     ArrayBufferObjectMaybeShared* buf = this;
     if (buf->is<ArrayBufferObject>())
         return buf->as<ArrayBufferObject>().dataPointerShared();
     return buf->as<SharedArrayBufferObject>().dataPointerShared();
 }
 
+inline bool
+ArrayBufferObjectMaybeShared::isDetached() const
+{
+    if (this->is<ArrayBufferObject>())
+        return this->as<ArrayBufferObject>().isDetached();
+    return false;
+}
+
 inline uint32_t
 AnyArrayBufferByteLength(const ArrayBufferObjectMaybeShared* buf)
 {
     if (buf->is<ArrayBufferObject>())
         return buf->as<ArrayBufferObject>().byteLength();
     return buf->as<SharedArrayBufferObject>().byteLength();
 }
 
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -75,16 +75,18 @@ ArrayBufferObjectMaybeShared& AsAnyArray
 
 class ArrayBufferObjectMaybeShared : public NativeObject
 {
   public:
     uint32_t byteLength() {
         return AnyArrayBufferByteLength(this);
     }
 
+    inline bool isDetached() const;
+
     inline SharedMem<uint8_t*> dataPointerEither();
 };
 
 /*
  * ArrayBufferObject
  *
  * This class holds the underlying raw buffer that the various ArrayBufferViews
  * (eg DataViewObject, the TypedArrays, TypedObjects) access. It can be created
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -630,59 +630,70 @@ class TypedArrayObjectTemplate : public 
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
             return nullptr; // byteOffset + len is too big for the arraybuffer
         }
 
         return makeInstance(cx, buffer, byteOffset, len, proto);
     }
 
     static bool
-    maybeCreateArrayBuffer(JSContext* cx, uint32_t nelements, MutableHandle<ArrayBufferObject*> buffer)
+    maybeCreateArrayBuffer(JSContext* cx, uint32_t nelements, HandleObject nonDefaultProto,
+                           MutableHandle<ArrayBufferObject*> buffer)
     {
         static_assert(INLINE_BUFFER_LIMIT % sizeof(NativeType) == 0,
                       "ArrayBuffer inline storage shouldn't waste any space");
 
-        if (nelements <= INLINE_BUFFER_LIMIT / sizeof(NativeType)) {
+        if (!nonDefaultProto && nelements <= INLINE_BUFFER_LIMIT / sizeof(NativeType)) {
             // The array's data can be inline, and the buffer created lazily.
             return true;
         }
 
         if (nelements >= INT32_MAX / sizeof(NativeType)) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
                                  JSMSG_NEED_DIET, "size and count");
             return false;
         }
 
-        ArrayBufferObject* buf = ArrayBufferObject::create(cx, nelements * sizeof(NativeType));
+        ArrayBufferObject* buf = ArrayBufferObject::create(cx, nelements * sizeof(NativeType),
+                                                           nonDefaultProto);
         if (!buf)
             return false;
 
         buffer.set(buf);
         return true;
     }
 
     static JSObject*
     fromLength(JSContext* cx, uint32_t nelements, HandleObject newTarget = nullptr)
     {
         RootedObject proto(cx);
         if (!GetPrototypeForInstance(cx, newTarget, &proto))
             return nullptr;
 
         Rooted<ArrayBufferObject*> buffer(cx);
-        if (!maybeCreateArrayBuffer(cx, nelements, &buffer))
+        if (!maybeCreateArrayBuffer(cx, nelements, nullptr, &buffer))
             return nullptr;
 
         return makeInstance(cx, buffer, 0, nelements, proto);
     }
 
+    static bool
+    AllocateArrayBuffer(JSContext* cx, HandleValue ctor, uint32_t elementLength,
+                        MutableHandle<ArrayBufferObject*> buffer);
+
+    static bool
+    CloneArrayBufferNoCopy(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> srcBuffer,
+                           bool isWrapped, uint32_t srcByteOffset,
+                           MutableHandle<ArrayBufferObject*> buffer);
+
     static JSObject*
     fromArray(JSContext* cx, HandleObject other, HandleObject newTarget = nullptr);
 
     static JSObject*
-    fromTypedArray(JSContext* cx, HandleObject other, HandleObject newTarget);
+    fromTypedArray(JSContext* cx, HandleObject other, bool isWrapped, HandleObject newTarget);
 
     static JSObject*
     fromObject(JSContext* cx, HandleObject other, HandleObject newTarget);
 
     static const NativeType
     getIndex(JSObject* obj, uint32_t index)
     {
         TypedArrayObject& tarray = obj->as<TypedArrayObject>();
@@ -713,71 +724,228 @@ typedef TypedArrayObjectTemplate<uint8_c
 } /* anonymous namespace */
 
 template<typename T>
 struct TypedArrayObject::OfType
 {
     typedef TypedArrayObjectTemplate<T> Type;
 };
 
+// ES 2016 draft Mar 25, 2016 24.1.1.1.
+template<typename T>
+/* static */ bool
+TypedArrayObjectTemplate<T>::AllocateArrayBuffer(JSContext* cx, HandleValue ctor,
+                                                 uint32_t elementLength,
+                                                 MutableHandle<ArrayBufferObject*> buffer)
+{
+    // ES 2016 draft Mar 25, 2016 24.1.1.1 step 1 (partially).
+    // ES 2016 draft Mar 25, 2016 9.1.14 steps 1-2.
+    MOZ_ASSERT(ctor.isObject());
+    RootedObject proto(cx);
+    RootedObject ctorObj(cx, &ctor.toObject());
+    if (!GetPrototypeFromConstructor(cx, ctorObj, &proto))
+        return false;
+    JSObject* arrayBufferProto = GlobalObject::getOrCreateArrayBufferPrototype(cx, cx->global());
+    if (!arrayBufferProto)
+        return false;
+    if (proto == arrayBufferProto)
+        proto = nullptr;
+
+    // ES 2016 draft Mar 25, 2016 24.1.1.1 steps 1 (remaining part), 2-6.
+    if (!maybeCreateArrayBuffer(cx, elementLength, proto, buffer))
+        return false;
+
+    return true;
+}
+
+static bool
+GetSpeciesConstructor(JSContext* cx, HandleObject obj, bool isWrapped, MutableHandleValue ctor)
+{
+    if (!isWrapped) {
+        if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_ArrayBuffer))
+            return false;
+        RootedValue defaultCtor(cx, cx->global()->getConstructor(JSProto_ArrayBuffer));
+        if (!SpeciesConstructor(cx, obj, defaultCtor, ctor))
+            return false;
+
+        return true;
+    }
+
+    {
+        JSAutoCompartment ac(cx, obj);
+        if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_ArrayBuffer))
+            return false;
+        RootedValue defaultCtor(cx, cx->global()->getConstructor(JSProto_ArrayBuffer));
+        if (!SpeciesConstructor(cx, obj, defaultCtor, ctor))
+            return false;
+    }
+
+    return JS_WrapValue(cx, ctor);
+}
+
+// ES 2016 draft Mar 25, 2016 24.1.1.4.
+template<typename T>
+/* static */ bool
+TypedArrayObjectTemplate<T>::CloneArrayBufferNoCopy(JSContext* cx,
+                                                    Handle<ArrayBufferObjectMaybeShared*> srcBuffer,
+                                                    bool isWrapped, uint32_t srcByteOffset,
+                                                    MutableHandle<ArrayBufferObject*> buffer)
+{
+    // Step 1 (skipped).
+
+    // Step 2.a.
+    RootedValue cloneCtor(cx);
+    if (!GetSpeciesConstructor(cx, srcBuffer, isWrapped, &cloneCtor))
+        return false;
+
+    // Step 2.b.
+    if (srcBuffer->isDetached()) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
+        return false;
+    }
+
+    // Step 3 (skipped).
+
+    // Steps 4-5.
+    uint32_t srcLength = srcBuffer->byteLength();
+    MOZ_ASSERT(srcByteOffset <= srcLength);
+
+    // Step 6.
+    uint32_t cloneLength = srcLength - srcByteOffset;
+
+    // Step 7 (skipped).
+
+    // Steps 8.
+    MOZ_ASSERT(cloneLength % BYTES_PER_ELEMENT == 0);
+    if (!AllocateArrayBuffer(cx, cloneCtor, cloneLength / BYTES_PER_ELEMENT, buffer))
+        return false;
+
+    // Step 9.
+    if (srcBuffer->isDetached()) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
+        return false;
+    }
+
+    // Steps 10-11 (done in caller).
+
+    // Step 12.
+    return true;
+}
+
 template<typename T>
 /* static */ JSObject*
 TypedArrayObjectTemplate<T>::fromArray(JSContext* cx, HandleObject other,
                                        HandleObject newTarget /* = nullptr */)
 {
     // Allow nullptr newTarget for FriendAPI methods, which don't care about
     // subclassing.
     if (other->is<TypedArrayObject>())
-        return fromTypedArray(cx, other, newTarget);
+        return fromTypedArray(cx, other, /* wrapped= */ false, newTarget);
+
+    if (other->is<WrapperObject>() && UncheckedUnwrap(other)->is<TypedArrayObject>())
+        return fromTypedArray(cx, other, /* wrapped= */ true, newTarget);
 
     return fromObject(cx, other, newTarget);
 }
 
 // ES 2016 draft Mar 25, 2016 22.2.4.3.
 template<typename T>
 /* static */ JSObject*
-TypedArrayObjectTemplate<T>::fromTypedArray(JSContext* cx, HandleObject other,
+TypedArrayObjectTemplate<T>::fromTypedArray(JSContext* cx, HandleObject other, bool isWrapped,
                                             HandleObject newTarget)
 {
     // Step 1.
-    MOZ_ASSERT(other->is<TypedArrayObject>());
+    MOZ_ASSERT_IF(!isWrapped, other->is<TypedArrayObject>());
+    MOZ_ASSERT_IF(isWrapped,
+                  other->is<WrapperObject>() &&
+                  UncheckedUnwrap(other)->is<TypedArrayObject>());
 
     // Step 2 (done in caller).
 
     // Step 4 (partially).
     RootedObject proto(cx);
     if (!GetPrototypeForInstance(cx, newTarget, &proto))
         return nullptr;
 
     // Step 5.
-    Rooted<TypedArrayObject*> srcArray(cx, &other->as<TypedArrayObject>());
-
-    // Steps 6-7.
-    if (srcArray->hasDetachedBuffer()) {
+    Rooted<TypedArrayObject*> srcArray(cx);
+    if (!isWrapped) {
+        srcArray = &other->as<TypedArrayObject>();
+        if (!TypedArrayObject::ensureHasBuffer(cx, srcArray))
+            return nullptr;
+    } else {
+        RootedObject unwrapped(cx, CheckedUnwrap(other));
+        if (!unwrapped) {
+            JS_ReportError(cx, "Permission denied to access object");
+            return nullptr;
+        }
+
+        JSAutoCompartment ac(cx, unwrapped);
+
+        srcArray = &unwrapped->as<TypedArrayObject>();
+        if (!TypedArrayObject::ensureHasBuffer(cx, srcArray))
+            return nullptr;
+    }
+
+    // Step 6.
+    Rooted<ArrayBufferObjectMaybeShared*> srcData(cx, srcArray->bufferEither());
+
+    // Step 7.
+    if (srcData->isDetached()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
         return nullptr;
     }
 
     // Steps 10.
     uint32_t elementLength = srcArray->length();
 
-    // Steps 8-9, 11-18.
+    // Steps 11-12.
+    Scalar::Type srcType = srcArray->type();
+
+    // Step 13 (skipped).
+
+    // Step 14.
+    uint32_t srcByteOffset = srcArray->byteOffset();
+
+    // Steps 15-16 (skipped).
+    // Our AllocateArrayBuffer receives elementLength instead of byteLength.
+
+    // Steps 8-9, 17.
     Rooted<ArrayBufferObject*> buffer(cx);
-    if (!maybeCreateArrayBuffer(cx, elementLength, &buffer))
-        return nullptr;
-
-    // Steps 3, 4 (remaining part), 19-23.
+    if (ArrayTypeID() == srcType) {
+        // Step 17.a.
+        if (!CloneArrayBufferNoCopy(cx, srcData, isWrapped, srcByteOffset, &buffer))
+            return nullptr;
+    } else {
+        // Step 18.a.
+        RootedValue bufferCtor(cx);
+        if (!GetSpeciesConstructor(cx, srcData, isWrapped, &bufferCtor))
+            return nullptr;
+
+        // Step 18.b.
+        if (!AllocateArrayBuffer(cx, bufferCtor, elementLength, &buffer))
+            return nullptr;
+
+        // Step 18.c.
+        if (srcArray->hasDetachedBuffer()) {
+            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
+            return nullptr;
+        }
+    }
+
+    // Steps 3, 4 (remaining part), 19-22.
     Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, elementLength, proto));
     if (!obj)
         return nullptr;
 
     // Step 18.d-g or 24.1.1.4 step 11.
     if (!TypedArrayMethods<TypedArrayObject>::setFromTypedArray(cx, obj, srcArray))
         return nullptr;
 
+    // Step 23.
     return obj;
 }
 
 // FIXME: This is not compatible with TypedArrayFrom in the spec
 // (ES 2016 draft Mar 25, 2016 22.2.4.4 and 22.2.2.1.1)
 // We should handle iterator protocol (bug 1232266).
 template<typename T>
 /* static */ JSObject*
@@ -785,17 +953,17 @@ TypedArrayObjectTemplate<T>::fromObject(
 {
     RootedObject proto(cx);
     Rooted<ArrayBufferObject*> buffer(cx);
     uint32_t len;
     if (!GetLengthProperty(cx, other, &len))
         return nullptr;
     if (!GetPrototypeForInstance(cx, newTarget, &proto))
         return nullptr;
-    if (!maybeCreateArrayBuffer(cx, len, &buffer))
+    if (!maybeCreateArrayBuffer(cx, len, nullptr, &buffer))
         return nullptr;
 
     Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, len, proto));
     if (!obj)
         return nullptr;
 
     if (!TypedArrayMethods<TypedArrayObject>::setFromNonTypedArray(cx, obj, other, len))
         return nullptr;