Bug 1345115 - Part 3: Update TypedArray constructor with ArrayBuffer argument to follow latest spec. r=Waldo
authorAndré Bargull <andre.bargull@gmail.com>
Wed, 19 Apr 2017 06:07:47 -0700
changeset 353996 b4bdc406fc1d750c0b19dd8a6ba7731f7072f5ce
parent 353995 8f44dcf0d3406f4d44b04b86fff06a37937f2179
child 353997 c1d7ee18b7d0bd59990d666473d47b8b31f4710b
push id31684
push usercbook@mozilla.com
push dateThu, 20 Apr 2017 09:13:26 +0000
treeherdermozilla-central@27311156637f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs1345115
milestone55.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 1345115 - Part 3: Update TypedArray constructor with ArrayBuffer argument to follow latest spec. r=Waldo
js/src/tests/ecma_6/TypedArray/constructor-buffer-sequence-nonstandard.js
js/src/tests/ecma_6/TypedArray/constructor-buffer-sequence.js
js/src/vm/TypedArrayObject.cpp
deleted file mode 100644
--- a/js/src/tests/ecma_6/TypedArray/constructor-buffer-sequence-nonstandard.js
+++ /dev/null
@@ -1,35 +0,0 @@
-// 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] )
-
-// Contrary to the ES2017 specification, we don't allow to create a TypedArray
-// object with a detached ArrayBuffer. We consider the current specification to
-// be incorrect.
-
-const otherGlobal = newGlobal();
-
-function* createBuffers() {
-    var lengths = [0, 8];
-    for (let length of lengths) {
-        let buffer = new ArrayBuffer(length);
-        yield {buffer, detach: () => detachArrayBuffer(buffer)};
-    }
-
-    for (let length of lengths) {
-        let buffer = new otherGlobal.ArrayBuffer(length);
-        yield {buffer, detach: () => otherGlobal.detachArrayBuffer(buffer)};
-    }
-}
-
-// Ensure we handle the case when ToIndex(length) detaches the array buffer.
-for (let {buffer, detach} of createBuffers()) {
-    let length = {
-        valueOf() {
-            detach();
-            return 0;
-        }
-    };
-
-    assertThrowsInstanceOf(() => new Int32Array(buffer, 0, length), TypeError);
-}
-
-if (typeof reportCompare === "function")
-    reportCompare(true, true);
--- a/js/src/tests/ecma_6/TypedArray/constructor-buffer-sequence.js
+++ b/js/src/tests/ecma_6/TypedArray/constructor-buffer-sequence.js
@@ -58,145 +58,173 @@ function ValueReturning(value, detach) {
 // Ensure step 4 |AllocateTypedArray| is executed before step 6 |ToIndex(byteOffset)|.
 for (let {buffer} of createBuffers()) {
     let constructor = ConstructorWithThrowingPrototype();
 
     assertThrowsInstanceOf(() =>
         Reflect.construct(Int32Array, [buffer, poisonedValue, 0], constructor), ExpectedError);
 }
 
-// Ensure step 4 |AllocateTypedArray| is executed before step 8 |IsDetachedBuffer(buffer)|.
+// Ensure step 4 |AllocateTypedArray| is executed before step 9 |IsDetachedBuffer(buffer)|.
 for (let {buffer, detach} of createBuffers()) {
     let constructor = ConstructorWithThrowingPrototype();
 
     detach();
     assertThrowsInstanceOf(() =>
         Reflect.construct(Int32Array, [buffer, 0, 0], constructor), ExpectedError);
 }
 
-// Ensure step 4 |AllocateTypedArray| is executed before step 8 |IsDetachedBuffer(buffer)|.
+// Ensure step 4 |AllocateTypedArray| is executed before step 9 |IsDetachedBuffer(buffer)|.
 // - Variant: Detach buffer dynamically.
 for (let {buffer, detach} of createBuffers()) {
     let constructor = ConstructorWithThrowingPrototype(detach);
 
     assertThrowsInstanceOf(() =>
         Reflect.construct(Int32Array, [buffer, 0, 0], constructor), ExpectedError);
 }
 
-// Ensure step 4 |AllocateTypedArray| is executed before step 11.a |ToIndex(length)|.
+// Ensure step 4 |AllocateTypedArray| is executed before step 8.a |ToIndex(length)|.
 for (let {buffer} of createBuffers()) {
     let constructor = ConstructorWithThrowingPrototype();
 
     assertThrowsInstanceOf(() =>
         Reflect.construct(Int32Array, [buffer, 0, poisonedValue], constructor), ExpectedError);
 }
 
-// Ensure step 6 |ToIndex(byteOffset)| is executed before step 8 |IsDetachedBuffer(buffer)|.
+// Ensure step 6 |ToIndex(byteOffset)| is executed before step 9 |IsDetachedBuffer(buffer)|.
 for (let {buffer, detach} of createBuffers()) {
     let byteOffset = ValueThrowing();
 
     detach();
     assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, 0), ExpectedError);
 }
 
-// Ensure step 6 |ToIndex(byteOffset)| is executed before step 8 |IsDetachedBuffer(buffer)|.
+// Ensure step 6 |ToIndex(byteOffset)| is executed before step 9 |IsDetachedBuffer(buffer)|.
 // - Variant: Detach buffer dynamically.
 for (let {buffer, detach} of createBuffers()) {
     let byteOffset = ValueThrowing(detach);
 
     assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, 0), ExpectedError);
 }
 
-// Ensure step 6 |ToIndex(byteOffset)| is executed before step 11.a |ToIndex(length)|.
+// Ensure step 6 |ToIndex(byteOffset)| is executed before step 8.a |ToIndex(length)|.
 for (let {buffer} of createBuffers()) {
     let byteOffset = ValueThrowing();
 
     assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, poisonedValue), ExpectedError);
 }
 
-// Ensure step 7 |offset modulo elementSize ≠ 0| is executed before step 8 |IsDetachedBuffer(buffer)|.
+// Ensure step 7 |offset modulo elementSize ≠ 0| is executed before step 9 |IsDetachedBuffer(buffer)|.
 for (let {buffer, detach} of createBuffers()) {
     let byteOffset = 1;
 
     detach();
     assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, 0), RangeError);
 }
 
-// Ensure step 7 |offset modulo elementSize ≠ 0| is executed before step 8 |IsDetachedBuffer(buffer)|.
+// Ensure step 7 |offset modulo elementSize ≠ 0| is executed before step 9 |IsDetachedBuffer(buffer)|.
 // - Variant: Detach buffer dynamically.
 for (let {buffer, detach} of createBuffers()) {
     let byteOffset = ValueReturning(1, detach);
 
     assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, 0), RangeError);
 }
 
-// Ensure step 7 |offset modulo elementSize ≠ 0| is executed before step 11.a |ToIndex(length)|.
+// Ensure step 7 |offset modulo elementSize ≠ 0| is executed before step 8.a |ToIndex(length)|.
 for (let {buffer} of createBuffers()) {
     assertThrowsInstanceOf(() => new Int32Array(buffer, 1, poisonedValue), RangeError);
 }
 
-// Ensure step 8 |IsDetachedBuffer(buffer)| is executed before step 10.a |bufferByteLength modulo elementSize ≠ 0|.
+// Ensure step 8.a |ToIndex(length)| is executed before step 9 |IsDetachedBuffer(buffer)|.
+for (let {buffer, detach} of createBuffers()) {
+    let byteOffset = 0;
+    let length = ValueThrowing();
+
+    detach();
+    assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), ExpectedError);
+}
+
+// Ensure step 8.a |ToIndex(length)| is executed before step 9 |IsDetachedBuffer(buffer)|.
+// - Variant: Detach buffer dynamically (1).
+for (let {buffer, detach} of createBuffers()) {
+    let byteOffset = ValueReturning(0, detach);
+    let length = ValueThrowing();
+
+    assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), ExpectedError);
+}
+
+// Ensure step 8.a |ToIndex(length)| is executed before step 9 |IsDetachedBuffer(buffer)|.
+// - Variant: Detach buffer dynamically (2).
+for (let {buffer, detach} of createBuffers()) {
+    let byteOffset = 0;
+    let length = ValueThrowing(detach);
+
+    assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), ExpectedError);
+}
+
+// Ensure step 9 |IsDetachedBuffer(buffer)| is executed before step 11.a |bufferByteLength modulo elementSize ≠ 0|.
 for (let {buffer, detach} of createBuffers([1, 9])) {
     let byteOffset = 0;
 
     detach();
     assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset), TypeError);
 }
 
-// Ensure step 8 |IsDetachedBuffer(buffer)| is executed before step 10.a |bufferByteLength modulo elementSize ≠ 0|.
+// Ensure step 9 |IsDetachedBuffer(buffer)| is executed before step 11.a |bufferByteLength modulo elementSize ≠ 0|.
 // - Variant: Detach buffer dynamically.
 for (let {buffer, detach} of createBuffers([1, 9])) {
     let byteOffset = ValueReturning(0, detach);
 
     assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset), TypeError);
 }
 
-// Ensure step 8 |IsDetachedBuffer(buffer)| is executed before step 10.c |newByteLength < 0|.
+// Ensure step 9 |IsDetachedBuffer(buffer)| is executed before step 11.c |newByteLength < 0|.
 for (let {buffer, detach} of createBuffers()) {
     let byteOffset = 64;
 
     detach();
     assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset), TypeError);
 }
 
-// Ensure step 8 |IsDetachedBuffer(buffer)| is executed before step 10.c |newByteLength < 0|.
+// Ensure step 9 |IsDetachedBuffer(buffer)| is executed before step 11.c |newByteLength < 0|.
 // - Variant: Detach buffer dynamically.
 for (let {buffer, detach} of createBuffers()) {
     let byteOffset = ValueReturning(64, detach);
 
     assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset), TypeError);
 }
 
-// Ensure step 8 |IsDetachedBuffer(buffer)| is executed before step 11.a |ToIndex(length)|.
-for (let {buffer, detach} of createBuffers()) {
-    let byteOffset = 0;
-
-    detach();
-    assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, poisonedValue), TypeError);
-}
-
-// Ensure step 8 |IsDetachedBuffer(buffer)| is executed before step 11.a |ToIndex(length)|.
-for (let {buffer, detach} of createBuffers()) {
-    let byteOffset = ValueReturning(0, detach);
-
-    assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, poisonedValue), TypeError);
-}
-
-// Ensure step 11.c |offset+newByteLength > bufferByteLength| is executed when buffer is detached.
+// Ensure step 9 |IsDetachedBuffer(buffer)| is executed before step 12.b |offset+newByteLength > bufferByteLength|.
 // - Case A: The given byteOffset is too large.
 for (let {buffer, detach} of createBuffers()) {
     let byteOffset = 64;
     let length = ValueReturning(0, detach);
 
-    assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), RangeError);
+    assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), TypeError);
 }
 
-// Ensure step 11.c |offset+newByteLength > bufferByteLength| is executed when buffer is detached.
+// Ensure step 9 |IsDetachedBuffer(buffer)| is executed before step 12.b |offset+newByteLength > bufferByteLength|.
 // - Case B: The given length is too large.
 for (let {buffer, detach} of createBuffers()) {
     let byteOffset = 0;
     let length = ValueReturning(64, detach);
 
-    assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), RangeError);
+    assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), TypeError);
+}
+
+// Ensure we handle the case when ToIndex(byteOffset) detaches the array buffer.
+for (let {buffer, detach} of createBuffers()) {
+    let byteOffset = ValueReturning(0, detach);
+    let length = 0;
+
+    assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), TypeError);
+}
+
+// Ensure we handle the case when ToIndex(length) detaches the array buffer.
+for (let {buffer, detach} of createBuffers()) {
+    let byteOffset = 0;
+    let length = ValueReturning(0, detach);
+
+    assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), TypeError);
 }
 
 if (typeof reportCompare === "function")
     reportCompare(true, true);
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -743,132 +743,89 @@ class TypedArrayObjectTemplate : public 
         // 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] )
 
         // Step 4.
         // 22.2.4.2.1 AllocateTypedArray, step 1.
         RootedObject proto(cx);
         if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
             return nullptr;
 
-        if (dataObj->is<ArrayBufferObjectMaybeShared>()) {
-            HandleArrayBufferObjectMaybeShared buffer = dataObj.as<ArrayBufferObjectMaybeShared>();
-
-            // WARNING: |buffer| may be detached after calling this function!
-            uint32_t bufferByteLength;
-            uint32_t byteOffset;
-            int32_t length;
-            if (!getConstructorArgsForBuffer(cx, buffer, args, &bufferByteLength, &byteOffset,
-                                             &length))
-            {
-                return nullptr;
-            }
-
-            return fromBufferSameCompartment(cx, buffer, bufferByteLength, byteOffset, length,
-                                             proto);
-        }
-
-        RootedArrayBufferObjectMaybeShared buffer(cx);
-        if (!checkedUnwrapArrayBuffer(cx, dataObj, &buffer))
-            return nullptr;
-
-        // WARNING: |buffer| may be detached after calling this function!
-        uint32_t bufferByteLength;
-        uint32_t byteOffset;
-        int32_t length;
-        if (!getConstructorArgsForBuffer(cx, buffer, args, &bufferByteLength, &byteOffset,
-                                         &length))
-        {
-            return nullptr;
-        }
-
-        return fromBufferWrapped(cx, buffer, bufferByteLength, byteOffset, length, proto);
-    }
-
-    // ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210
-    // 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] )
-    // Steps 6-9, 11.a.
-    static bool
-    getConstructorArgsForBuffer(JSContext* cx,
-                                HandleArrayBufferObjectMaybeShared bufferMaybeUnwrapped,
-                                const CallArgs& args, uint32_t* bufferByteLengthPtr,
-                                uint32_t* byteOffsetPtr, int32_t* lengthPtr)
-    {
         int32_t byteOffset = 0;
         if (args.hasDefined(1)) {
             // Step 6.
             if (!ToInt32(cx, args[1], &byteOffset))
-                return false;
+                return nullptr;
             if (byteOffset < 0) {
                 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                           JSMSG_TYPED_ARRAY_NEGATIVE_ARG,
                                           "1");
-                return false;
+                return nullptr;
             }
 
             // Step 7.
             if (byteOffset % sizeof(NativeType) != 0) {
                 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                           JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
-                return false;
+                return nullptr;
             }
         }
 
-        // Step 8.
-        if (bufferMaybeUnwrapped->isDetached()) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
-            return false;
-        }
-
-        // Step 9.
-        uint32_t bufferByteLength = bufferMaybeUnwrapped->byteLength();
-
         int32_t length = -1;
         if (args.hasDefined(2)) {
-            // Step 11.a.
+            // Step 8.a.
             if (!ToInt32(cx, args[2], &length))
-                return false;
+                return nullptr;
             if (length < 0) {
                 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                           JSMSG_TYPED_ARRAY_NEGATIVE_ARG,
                                           "2");
-                return false;
+                return nullptr;
             }
-
-            // Step 11.c (Performed by callers through computeAndCheckLength).
         }
 
-        *bufferByteLengthPtr = bufferByteLength;
-        *byteOffsetPtr = uint32_t(byteOffset);
-        *lengthPtr = length;
-        return true;
+        // Steps 9-17.
+        if (dataObj->is<ArrayBufferObjectMaybeShared>()) {
+            HandleArrayBufferObjectMaybeShared buffer = dataObj.as<ArrayBufferObjectMaybeShared>();
+            return fromBufferSameCompartment(cx, buffer, byteOffset, length, proto);
+        }
+        return fromBufferWrapped(cx, dataObj, byteOffset, length, proto);
     }
 
-    // ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210
+    // ES2018 draft rev 8340bf9a8427ea81bb0d1459471afbcc91d18add
     // 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] )
-    // Steps 10.a-c, 11.b-c.
+    // Steps 9-12.
     static bool
-    computeAndCheckLength(JSContext* cx, uint32_t bufferByteLength, uint32_t byteOffset,
-                          int32_t lengthInt, uint32_t* length)
+    computeAndCheckLength(JSContext* cx, HandleArrayBufferObjectMaybeShared bufferMaybeUnwrapped,
+                          uint32_t byteOffset, int32_t lengthInt, uint32_t* length)
     {
         MOZ_ASSERT(byteOffset % sizeof(NativeType) == 0);
 
-        // Steps 10.c, 11.c.
+        // Step 9.
+        if (bufferMaybeUnwrapped->isDetached()) {
+           JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
+           return false;
+        }
+
+        // Step 10.
+        uint32_t bufferByteLength = bufferMaybeUnwrapped->byteLength();
+
+        // 11.c, 12.b.
         if (byteOffset > bufferByteLength) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                       JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
             return false; // invalid byteOffset
         }
 
         uint32_t len;
         if (lengthInt < 0) {
-            // Step 10.b.
+            // Step 11.b.
             uint32_t newByteLength = bufferByteLength - byteOffset;
             len = newByteLength / sizeof(NativeType);
 
-            // Step 10.a.
+            // Step 11.a.
             if (len * sizeof(NativeType) != newByteLength) {
                 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                           JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
                 return false; // given byte array doesn't map exactly to sizeof(NativeType) * N
             }
 
             // ArrayBuffer is too large for TypedArrays:
             // Standalone ArrayBuffers can hold up to INT32_MAX bytes, whereas
@@ -876,61 +833,53 @@ class TypedArrayObjectTemplate : public 
             // |INT32_MAX - sizeof(NativeType) - INT32_MAX % sizeof(NativeType)|
             // bytes.
             if (len >= INT32_MAX / sizeof(NativeType)) {
                 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                           JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
                 return false;
             }
         } else {
-            // Step 11.b (implicit).
+            // Step 12.a (implicit).
             len = uint32_t(lengthInt);
 
-            // Step 11.c.
+            // Step 12.b.
             if (len >= INT32_MAX / sizeof(NativeType)) {
                 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                           JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
                 return false;
             }
             uint32_t newByteLength = len * sizeof(NativeType);
 
-            // Step 11.c (|byteOffset| moved to the RHS to avoid overflow).
+            // Step 12.b (|byteOffset| moved to the RHS to avoid overflow).
             if (newByteLength > bufferByteLength - byteOffset) {
                 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                           JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
                 return false; // |byteOffset + newByteLength| is too big for the arraybuffer
             }
         }
 
         *length = len;
         return true;
     }
 
+    // ES2018 draft rev 8340bf9a8427ea81bb0d1459471afbcc91d18add
+    // 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] )
+    // Steps 9-17.
     static JSObject*
-    fromBufferSameCompartment(JSContext* cx,
-                              HandleArrayBufferObjectMaybeShared bufferMaybeDetached,
-                              uint32_t bufferByteLength, uint32_t byteOffset, int32_t lengthInt,
-                              HandleObject proto)
+    fromBufferSameCompartment(JSContext* cx, HandleArrayBufferObjectMaybeShared buffer,
+                              uint32_t byteOffset, int32_t lengthInt, HandleObject proto)
     {
+        // Steps 9-12.
         uint32_t length;
-        if (!computeAndCheckLength(cx, bufferByteLength, byteOffset, lengthInt, &length))
+        if (!computeAndCheckLength(cx, buffer, byteOffset, lengthInt, &length))
             return nullptr;
 
-        // The specification allows to create a typed array with a detached
-        // array buffer (ToIndex in step 11.a can detach the array buffer). We
-        // don't allow this to happen and instead throw a TypeError. This is
-        // observable from the user's point of view, but we consider the
-        // current specification to be incorrect.
-        if (bufferMaybeDetached->isDetached()) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
-            return nullptr;
-        }
-        MOZ_ASSERT(bufferMaybeDetached->byteLength() == bufferByteLength);
-
-        return makeInstance(cx, bufferMaybeDetached, byteOffset, length, proto);
+        // Steps 13-17.
+        return makeInstance(cx, buffer, byteOffset, length, proto);
     }
 
     // Create a TypedArray object in another compartment.
     //
     // ES6 supports creating a TypedArray in global A (using global A's
     // TypedArray constructor) backed by an ArrayBuffer created in global B.
     //
     // Our TypedArrayObject implementation doesn't support a TypedArray in
@@ -938,116 +887,79 @@ class TypedArrayObjectTemplate : public 
     // case, we create the TypedArray in B (!) and return a cross-compartment
     // wrapper.
     //
     // Extra twist: the spec says the new TypedArray's [[Prototype]] must be
     // A's TypedArray.prototype. So even though we're creating the TypedArray
     // in B, its [[Prototype]] must be (a cross-compartment wrapper for) the
     // TypedArray.prototype in A.
     static JSObject*
-    fromBufferWrapped(JSContext* cx,
-                      HandleArrayBufferObjectMaybeShared unwrappedBufferMaybeDetached,
-                      uint32_t bufferByteLength, uint32_t byteOffset, int32_t lengthInt,
+    fromBufferWrapped(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt,
                       HandleObject proto)
     {
-        uint32_t length;
-        if (!computeAndCheckLength(cx, bufferByteLength, byteOffset, lengthInt, &length))
-            return nullptr;
-
-        // The specification allows to create a typed array with a detached
-        // array buffer (ToIndex in step 11.a can detach the array buffer). We
-        // don't allow this to happen and instead throw a TypeError. This is
-        // observable from the user's point of view, but we consider the
-        // current specification to be incorrect.
-        if (unwrappedBufferMaybeDetached->isDetached()) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
+        JSObject* unwrapped = CheckedUnwrap(bufobj);
+        if (!unwrapped) {
+            ReportAccessDenied(cx);
             return nullptr;
         }
-        MOZ_ASSERT(unwrappedBufferMaybeDetached->byteLength() == bufferByteLength);
+
+        if (!unwrapped->is<ArrayBufferObjectMaybeShared>()) {
+            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
+            return nullptr;
+        }
+
+        RootedArrayBufferObjectMaybeShared unwrappedBuffer(cx);
+        unwrappedBuffer = &unwrapped->as<ArrayBufferObjectMaybeShared>();
+
+        uint32_t length;
+        if (!computeAndCheckLength(cx, unwrappedBuffer, byteOffset, lengthInt, &length))
+            return nullptr;
 
         // Make sure to get the [[Prototype]] for the created typed array from
         // this compartment.
         RootedObject protoRoot(cx, proto);
         if (!protoRoot) {
             if (!GetBuiltinPrototype(cx, JSCLASS_CACHED_PROTO_KEY(instanceClass()), &protoRoot))
                 return nullptr;
         }
 
         RootedObject typedArray(cx);
         {
-            JSAutoCompartment ac(cx, unwrappedBufferMaybeDetached);
+            JSAutoCompartment ac(cx, unwrappedBuffer);
 
             RootedObject wrappedProto(cx, protoRoot);
             if (!cx->compartment()->wrap(cx, &wrappedProto))
                 return nullptr;
 
-            typedArray =
-                makeInstance(cx, unwrappedBufferMaybeDetached, byteOffset, length, wrappedProto);
+            typedArray = makeInstance(cx, unwrappedBuffer, byteOffset, length, wrappedProto);
             if (!typedArray)
                 return nullptr;
         }
 
         if (!cx->compartment()->wrap(cx, &typedArray))
             return nullptr;
 
         return typedArray;
     }
 
-    static bool
-    checkedUnwrapArrayBuffer(JSContext* cx, HandleObject bufobj,
-                             MutableHandleArrayBufferObjectMaybeShared buffer)
-    {
-        JSObject* unwrapped = CheckedUnwrap(bufobj);
-        if (!unwrapped) {
-            ReportAccessDenied(cx);
-            return false;
-        }
-
-        if (!unwrapped->is<ArrayBufferObjectMaybeShared>()) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-            return false;
-        }
-
-        buffer.set(&unwrapped->as<ArrayBufferObjectMaybeShared>());
-        return true;
-    }
-
   public:
     static JSObject*
     fromBuffer(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt)
     {
         if (byteOffset % sizeof(NativeType) != 0) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                       JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
             return nullptr; // invalid byteOffset
         }
 
         if (bufobj->is<ArrayBufferObjectMaybeShared>()) {
             HandleArrayBufferObjectMaybeShared buffer = bufobj.as<ArrayBufferObjectMaybeShared>();
-
-            if (buffer->isDetached()) {
-                JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                                          JSMSG_TYPED_ARRAY_DETACHED);
-                return nullptr;
-            }
-
-            return fromBufferSameCompartment(cx, buffer, buffer->byteLength(), byteOffset,
-                                             lengthInt, nullptr);
+            return fromBufferSameCompartment(cx, buffer, byteOffset, lengthInt, nullptr);
         }
-
-        RootedArrayBufferObjectMaybeShared buffer(cx);
-        if (!checkedUnwrapArrayBuffer(cx, bufobj, &buffer))
-            return nullptr;
-
-        if (buffer->isDetached()) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
-            return nullptr;
-        }
-
-        return fromBufferWrapped(cx, buffer, buffer->byteLength(), byteOffset, lengthInt, nullptr);
+        return fromBufferWrapped(cx, bufobj, byteOffset, lengthInt, nullptr);
     }
 
     static bool
     maybeCreateArrayBuffer(JSContext* cx, uint32_t count, uint32_t unit,
                            HandleObject nonDefaultProto,
                            MutableHandle<ArrayBufferObject*> buffer)
     {
         if (count >= INT32_MAX / unit) {