Bug 1345115 - Part 1: Don't switch compartment when typed array constructor is called with arraybuffer from other compartment. r=Waldo
authorAndré Bargull <andre.bargull@gmail.com>
Wed, 19 Apr 2017 06:07:16 -0700
changeset 353994 4126ffda915309da1578a2b57d8d726000cc91f0
parent 353993 feb9622d58ab943909b06461da6c5fa8658186e4
child 353995 8f44dcf0d3406f4d44b04b86fff06a37937f2179
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 1: Don't switch compartment when typed array constructor is called with arraybuffer from other compartment. r=Waldo
js/public/Class.h
js/src/builtin/DataViewObject.cpp
js/src/tests/ecma_6/TypedArray/constructor-buffer-sequence-nonstandard.js
js/src/tests/ecma_6/TypedArray/constructor-buffer-sequence.js
js/src/vm/ArrayBufferObject.h
js/src/vm/GlobalObject.h
js/src/vm/TypedArrayObject.cpp
js/src/vm/TypedArrayObject.h
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -850,17 +850,17 @@ static const uint32_t JSCLASS_FOREGROUND
 // with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
 // previously allowed, but is now an ES5 violation and thus unsupported.
 //
 // JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at
 // the beginning of every global object's slots for use by the
 // application.
 static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5;
 static const uint32_t JSCLASS_GLOBAL_SLOT_COUNT =
-    JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 46;
+    JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 37;
 
 #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n)                              \
     (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
 #define JSCLASS_GLOBAL_FLAGS                                                  \
     JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0)
 #define JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(clasp)                              \
   (((clasp)->flags & JSCLASS_IS_GLOBAL)                                       \
    && JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT)
--- a/js/src/builtin/DataViewObject.cpp
+++ b/js/src/builtin/DataViewObject.cpp
@@ -232,22 +232,16 @@ DataViewObject::constructSameCompartment
 // Our DataViewObject implementation doesn't support a DataView in
 // compartment A backed by an ArrayBuffer in compartment B. So in this case,
 // we create the DataView in B (!) and return a cross-compartment wrapper.
 //
 // Extra twist: the spec says the new DataView's [[Prototype]] must be
 // A's DataView.prototype. So even though we're creating the DataView in B,
 // its [[Prototype]] must be (a cross-compartment wrapper for) the
 // DataView.prototype in A.
-//
-// As if this were not confusing enough, the way we actually do this is also
-// tricky. We call compartment A's createDataViewForThis method, passing it
-// bufobj as `this`. That calls ArrayBufferObject::createDataViewForThis(),
-// which uses CallNonGenericMethod to switch to compartment B so that
-// the new DataView is created there.
 bool
 DataViewObject::constructWrapped(JSContext* cx, HandleObject bufobj, const CallArgs& args)
 {
     MOZ_ASSERT(args.isConstructing());
     MOZ_ASSERT(bufobj->is<WrapperObject>());
 
     RootedObject unwrapped(cx, CheckedUnwrap(bufobj));
     if (!unwrapped) {
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/constructor-buffer-sequence-nonstandard.js
@@ -0,0 +1,35 @@
+// 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);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/constructor-buffer-sequence.js
@@ -0,0 +1,202 @@
+// 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] )
+
+// Ensure the various error conditions are tested in the correct order.
+
+const otherGlobal = newGlobal();
+
+function* createBuffers(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)};
+    }
+}
+
+const poisonedValue = new Proxy({}, new Proxy({}, {
+    get() {
+        // Throws an exception when any proxy trap is invoked.
+        throw new Error("Poisoned Value");
+    }
+}));
+
+class ExpectedError extends Error { }
+
+function ConstructorWithThrowingPrototype(detach) {
+    return Object.defineProperty(function(){}.bind(null), "prototype", {
+        get() {
+            if (detach)
+                detach();
+            throw new ExpectedError();
+        }
+    });
+}
+
+function ValueThrowing(detach) {
+    return {
+        valueOf() {
+            if (detach)
+                detach();
+            throw new ExpectedError();
+        }
+    };
+}
+
+function ValueReturning(value, detach) {
+    return {
+        valueOf() {
+            if (detach)
+                detach();
+            return value;
+        }
+    };
+}
+
+// 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)|.
+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)|.
+// - 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)|.
+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)|.
+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)|.
+// - 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)|.
+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)|.
+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)|.
+// - 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)|.
+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|.
+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|.
+// - 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|.
+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|.
+// - 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.
+// - 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);
+}
+
+// Ensure step 11.c |offset+newByteLength > bufferByteLength| is executed when buffer is detached.
+// - 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);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -249,21 +249,16 @@ class ArrayBufferObject : public ArrayBu
     static ArrayBufferObject* create(JSContext* cx, uint32_t nbytes,
                                      HandleObject proto = nullptr,
                                      NewObjectKind newKind = GenericObject);
 
     // Create an ArrayBufferObject that is safely finalizable and can later be
     // initialize()d to become a real, content-visible ArrayBufferObject.
     static ArrayBufferObject* createEmpty(JSContext* cx);
 
-    template<typename T>
-    static bool createTypedArrayFromBufferImpl(JSContext* cx, const CallArgs& args);
-    template<typename T>
-    static bool createTypedArrayFromBuffer(JSContext* cx, unsigned argc, Value* vp);
-
     static void copyData(Handle<ArrayBufferObject*> toBuffer, uint32_t toIndex,
                          Handle<ArrayBufferObject*> fromBuffer, uint32_t fromIndex,
                          uint32_t count);
 
     static void trace(JSTracer* trc, JSObject* obj);
     static void objectMoved(JSObject* obj, const JSObject* old);
 
     static BufferContents externalizeContents(JSContext* cx,
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -67,30 +67,16 @@ class GlobalObject : public NativeObject
      */
     static const unsigned STANDARD_CLASS_SLOTS = JSProto_LIMIT * 2;
 
     enum : unsigned {
         /* Various function values needed by the engine. */
         EVAL = APPLICATION_SLOTS + STANDARD_CLASS_SLOTS,
         THROWTYPEERROR,
 
-        /*
-         * Instances of the internal createArrayFromBuffer function used by the
-         * typed array code, one per typed array element type.
-         */
-        FROM_BUFFER_UINT8,
-        FROM_BUFFER_INT8,
-        FROM_BUFFER_UINT16,
-        FROM_BUFFER_INT16,
-        FROM_BUFFER_UINT32,
-        FROM_BUFFER_INT32,
-        FROM_BUFFER_FLOAT32,
-        FROM_BUFFER_FLOAT64,
-        FROM_BUFFER_UINT8CLAMPED,
-
         /* One-off properties stored after slots for built-ins. */
         LEXICAL_ENVIRONMENT,
         EMPTY_GLOBAL_SCOPE,
         ITERATOR_PROTO,
         ARRAY_ITERATOR_PROTO,
         STRING_ITERATOR_PROTO,
         LEGACY_GENERATOR_OBJECT_PROTO,
         STAR_GENERATOR_OBJECT_PROTO,
@@ -255,31 +241,16 @@ class GlobalObject : public NativeObject
     }
     bool errorClassesInitialized() const {
         return classIsInitialized(JSProto_Error);
     }
     bool dataViewClassInitialized() const {
         return classIsInitialized(JSProto_DataView);
     }
 
-    Value createArrayFromBufferHelper(uint32_t slot) const {
-        MOZ_ASSERT(FROM_BUFFER_UINT8 <= slot && slot <= FROM_BUFFER_UINT8CLAMPED);
-        return getSlot(slot);
-    }
-
-    void setCreateArrayFromBufferHelper(uint32_t slot, Handle<JSFunction*> fun) {
-        MOZ_ASSERT(getSlotRef(slot).isUndefined());
-        setSlot(slot, ObjectValue(*fun));
-    }
-
-  public:
-    template<typename T>
-    inline void setCreateArrayFromBuffer(Handle<JSFunction*> fun);
-
-  private:
     // Disallow use of unqualified JSObject::create in GlobalObject.
     static GlobalObject* create(...) = delete;
 
     friend struct ::JSRuntime;
     static GlobalObject* createInternal(JSContext* cx, const Class* clasp);
 
   public:
     static GlobalObject*
@@ -774,19 +745,16 @@ class GlobalObject : public NativeObject
 
     JSObject* getThrowTypeError() const {
         const Value v = getReservedSlot(THROWTYPEERROR);
         MOZ_ASSERT(v.isObject(),
                    "attempting to access [[ThrowTypeError]] too early");
         return &v.toObject();
     }
 
-    template<typename T>
-    inline Value createArrayFromBuffer() const;
-
     static bool isRuntimeCodeGenEnabled(JSContext* cx, Handle<GlobalObject*> global);
 
     // Warn about use of the deprecated watch/unwatch functions in the global
     // in which |obj| was created, if no prior warning was given.
     static bool warnOnceAboutWatch(JSContext* cx, HandleObject obj) {
         // Temporarily disabled until we've provided a watch/unwatch workaround for
         // debuggers like Firebug (bug 934669).
         //return warnOnceAbout(cx, obj, WARN_WATCH_DEPRECATED, JSMSG_OBJECT_WATCH_DEPRECATED);
@@ -884,142 +852,16 @@ class GlobalObject : public NativeObject
     }
 
     // Returns either this global's star-generator function prototype, or null
     // if that object was never created.  Dodgy; for use only in also-dodgy
     // GlobalHelperThreadState::mergeParseTaskCompartment().
     JSObject* getStarGeneratorFunctionPrototype();
 };
 
-template<>
-inline void
-GlobalObject::setCreateArrayFromBuffer<uint8_t>(Handle<JSFunction*> fun)
-{
-    setCreateArrayFromBufferHelper(FROM_BUFFER_UINT8, fun);
-}
-
-template<>
-inline void
-GlobalObject::setCreateArrayFromBuffer<int8_t>(Handle<JSFunction*> fun)
-{
-    setCreateArrayFromBufferHelper(FROM_BUFFER_INT8, fun);
-}
-
-template<>
-inline void
-GlobalObject::setCreateArrayFromBuffer<uint16_t>(Handle<JSFunction*> fun)
-{
-    setCreateArrayFromBufferHelper(FROM_BUFFER_UINT16, fun);
-}
-
-template<>
-inline void
-GlobalObject::setCreateArrayFromBuffer<int16_t>(Handle<JSFunction*> fun)
-{
-    setCreateArrayFromBufferHelper(FROM_BUFFER_INT16, fun);
-}
-
-template<>
-inline void
-GlobalObject::setCreateArrayFromBuffer<uint32_t>(Handle<JSFunction*> fun)
-{
-    setCreateArrayFromBufferHelper(FROM_BUFFER_UINT32, fun);
-}
-
-template<>
-inline void
-GlobalObject::setCreateArrayFromBuffer<int32_t>(Handle<JSFunction*> fun)
-{
-    setCreateArrayFromBufferHelper(FROM_BUFFER_INT32, fun);
-}
-
-template<>
-inline void
-GlobalObject::setCreateArrayFromBuffer<float>(Handle<JSFunction*> fun)
-{
-    setCreateArrayFromBufferHelper(FROM_BUFFER_FLOAT32, fun);
-}
-
-template<>
-inline void
-GlobalObject::setCreateArrayFromBuffer<double>(Handle<JSFunction*> fun)
-{
-    setCreateArrayFromBufferHelper(FROM_BUFFER_FLOAT64, fun);
-}
-
-template<>
-inline void
-GlobalObject::setCreateArrayFromBuffer<uint8_clamped>(Handle<JSFunction*> fun)
-{
-    setCreateArrayFromBufferHelper(FROM_BUFFER_UINT8CLAMPED, fun);
-}
-
-template<>
-inline Value
-GlobalObject::createArrayFromBuffer<uint8_t>() const
-{
-    return createArrayFromBufferHelper(FROM_BUFFER_UINT8);
-}
-
-template<>
-inline Value
-GlobalObject::createArrayFromBuffer<int8_t>() const
-{
-    return createArrayFromBufferHelper(FROM_BUFFER_INT8);
-}
-
-template<>
-inline Value
-GlobalObject::createArrayFromBuffer<uint16_t>() const
-{
-    return createArrayFromBufferHelper(FROM_BUFFER_UINT16);
-}
-
-template<>
-inline Value
-GlobalObject::createArrayFromBuffer<int16_t>() const
-{
-    return createArrayFromBufferHelper(FROM_BUFFER_INT16);
-}
-
-template<>
-inline Value
-GlobalObject::createArrayFromBuffer<uint32_t>() const
-{
-    return createArrayFromBufferHelper(FROM_BUFFER_UINT32);
-}
-
-template<>
-inline Value
-GlobalObject::createArrayFromBuffer<int32_t>() const
-{
-    return createArrayFromBufferHelper(FROM_BUFFER_INT32);
-}
-
-template<>
-inline Value
-GlobalObject::createArrayFromBuffer<float>() const
-{
-    return createArrayFromBufferHelper(FROM_BUFFER_FLOAT32);
-}
-
-template<>
-inline Value
-GlobalObject::createArrayFromBuffer<double>() const
-{
-    return createArrayFromBufferHelper(FROM_BUFFER_FLOAT64);
-}
-
-template<>
-inline Value
-GlobalObject::createArrayFromBuffer<uint8_clamped>() const
-{
-    return createArrayFromBufferHelper(FROM_BUFFER_UINT8CLAMPED);
-}
-
 /*
  * Unless otherwise specified, define ctor.prototype = proto as non-enumerable,
  * non-configurable, and non-writable; and define proto.constructor = ctor as
  * non-enumerable but configurable and writable.
  */
 extern bool
 LinkConstructorAndPrototype(JSContext* cx, JSObject* ctor, JSObject* proto,
                             unsigned prototypeAttrs = JSPROP_PERMANENT | JSPROP_READONLY,
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -379,38 +379,16 @@ class TypedArrayObjectTemplate : public 
                                                SingletonObject);
 
         if (fun)
             fun->setJitInfo(&jit::JitInfo_TypedArrayConstructor);
 
         return fun;
     }
 
-    static bool
-    getOrCreateCreateArrayFromBufferFunction(JSContext* cx, MutableHandleValue fval)
-    {
-        RootedValue cache(cx, cx->global()->createArrayFromBuffer<NativeType>());
-        if (cache.isObject()) {
-            MOZ_ASSERT(cache.toObject().is<JSFunction>());
-            fval.set(cache);
-            return true;
-        }
-
-        RootedFunction fun(cx);
-        fun = NewNativeFunction(cx, ArrayBufferObject::createTypedArrayFromBuffer<NativeType>,
-                                0, nullptr);
-        if (!fun)
-            return false;
-
-        cx->global()->setCreateArrayFromBuffer<NativeType>(fun);
-
-        fval.setObject(*fun);
-        return true;
-    }
-
     static inline const Class* instanceClass()
     {
         return TypedArrayObject::classForType(ArrayTypeID());
     }
 
     static bool is(HandleValue v) {
         return v.isObject() && v.toObject().hasClass(instanceClass());
     }
@@ -478,16 +456,17 @@ class TypedArrayObjectTemplate : public 
         return &obj->as<TypedArrayObject>();
     }
 
     static TypedArrayObject*
     makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, uint32_t byteOffset, uint32_t len,
                  HandleObject proto)
     {
         MOZ_ASSERT_IF(!buffer, byteOffset == 0);
+        MOZ_ASSERT_IF(buffer, !buffer->isDetached());
 
         gc::AllocKind allocKind = buffer
                                   ? GetGCObjectKind(instanceClass())
                                   : AllocKindForLazyBuffer(len * sizeof(NativeType));
 
         // Subclassing mandates that we hand in the proto every time. Most of
         // the time, though, that [[Prototype]] will not be interesting. If
         // it isn't, we can do some more TI optimizations.
@@ -570,24 +549,16 @@ class TypedArrayObjectTemplate : public 
             if (!buffer->as<ArrayBufferObject>().addView(cx, obj))
                 return nullptr;
         }
 
         return obj;
     }
 
     static TypedArrayObject*
-    makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
-                 uint32_t byteOffset, uint32_t len)
-    {
-        RootedObject proto(cx, nullptr);
-        return makeInstance(cx, buffer, byteOffset, len, proto);
-    }
-
-    static TypedArrayObject*
     makeTemplateObject(JSContext* cx, int32_t len)
     {
         MOZ_ASSERT(len >= 0);
         size_t nbytes;
         MOZ_ALWAYS_TRUE(CalculateAllocSize<NativeType>(len, &nbytes));
         MOZ_ASSERT(nbytes < TypedArrayObject::SINGLETON_BYTE_LENGTH);
         NewObjectKind newKind = TenuredObject;
         bool fitsInline = nbytes <= INLINE_BUFFER_LIMIT;
@@ -730,16 +701,17 @@ class TypedArrayObjectTemplate : public 
 
         JSObject* obj = create(cx, args);
         if (!obj)
             return false;
         args.rval().setObject(*obj);
         return true;
     }
 
+  private:
     static JSObject*
     create(JSContext* cx, const CallArgs& args)
     {
         MOZ_ASSERT(args.isConstructing());
         RootedObject newTarget(cx, &args.newTarget().toObject());
 
         /* () or (number) */
         uint32_t len = 0;
@@ -762,170 +734,301 @@ class TypedArrayObjectTemplate : public 
          * Otherwise create a new typed array and copy elements 0..len-1
          * properties from the object, treating it as some sort of array.
          * Note that offset and length will be ignored.  Note that a
          * shared array's values are copied here.
          */
         if (!UncheckedUnwrap(dataObj)->is<ArrayBufferObjectMaybeShared>())
             return fromArray(cx, dataObj, newTarget);
 
-        /* (ArrayBuffer, [byteOffset, [length]]) */
+        // 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 nullptr;
+                return false;
             if (byteOffset < 0) {
                 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                           JSMSG_TYPED_ARRAY_NEGATIVE_ARG,
                                           "1");
-                return nullptr;
+                return false;
+            }
+
+            // Step 7.
+            if (byteOffset % sizeof(NativeType) != 0) {
+                JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+                                          JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
+                return false;
             }
         }
 
+        // 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.
             if (!ToInt32(cx, args[2], &length))
-                return nullptr;
+                return false;
             if (length < 0) {
                 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                           JSMSG_TYPED_ARRAY_NEGATIVE_ARG,
                                           "2");
-                return nullptr;
-            }
-        }
-
-        return fromBufferWithProto(cx, dataObj, byteOffset, length, proto);
-    }
-
-  public:
-    static JSObject*
-    fromBuffer(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt) {
-        return fromBufferWithProto(cx, bufobj, byteOffset, lengthInt, nullptr);
-    }
-
-    static JSObject*
-    fromBufferWithProto(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt,
-                        HandleObject proto)
-    {
-        if (bufobj->is<ProxyObject>()) {
-            /*
-             * Normally, NonGenericMethodGuard handles the case of transparent
-             * wrappers. However, we have a peculiar situation: we want to
-             * construct the new typed array in the compartment of the buffer,
-             * so that the typed array can point directly at their buffer's
-             * data without crossing compartment boundaries. So we use the
-             * machinery underlying NonGenericMethodGuard directly to proxy the
-             * native call. We will end up with a wrapper in the origin
-             * compartment for a view in the target compartment referencing the
-             * ArrayBufferObject in that same compartment.
-             */
-            JSObject* wrapped = CheckedUnwrap(bufobj);
-            if (!wrapped) {
-                ReportAccessDenied(cx);
-                return nullptr;
-            }
-
-            if (!IsAnyArrayBuffer(wrapped)) {
-                JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-                return nullptr; // must be arrayBuffer
+                return false;
             }
 
-            /*
-             * And for even more fun, the new view's prototype should be
-             * set to the origin compartment's prototype object, not the
-             * target's (specifically, the actual view in the target
-             * compartment will use as its prototype a wrapper around the
-             * origin compartment's view.prototype object).
-             *
-             * Rather than hack some crazy solution together, implement
-             * this all using a private helper function, created when
-             * ArrayBufferObject was initialized and cached in the global.
-             * This reuses all the existing cross-compartment crazy so we
-             * don't have to do anything *uniquely* crazy here.
-             */
-
-            RootedObject protoRoot(cx, proto);
-            if (!protoRoot) {
-                if (!GetBuiltinPrototype(cx, JSCLASS_CACHED_PROTO_KEY(instanceClass()), &protoRoot))
-                    return nullptr;
-            }
-
-            FixedInvokeArgs<3> args(cx);
-
-            args[0].setNumber(byteOffset);
-            args[1].setInt32(lengthInt);
-            args[2].setObject(*protoRoot);
-
-            RootedValue fval(cx);
-            if (!getOrCreateCreateArrayFromBufferFunction(cx, &fval))
-                return nullptr;
-
-            RootedValue thisv(cx, ObjectValue(*bufobj));
-            RootedValue rval(cx);
-            if (!js::Call(cx, fval, thisv, args, &rval))
-                return nullptr;
-
-            return &rval.toObject();
+            // Step 11.c (Performed by callers through computeAndCheckLength).
         }
 
-        if (!IsAnyArrayBuffer(bufobj)) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-            return nullptr; // must be arrayBuffer
-        }
+        *bufferByteLengthPtr = bufferByteLength;
+        *byteOffsetPtr = uint32_t(byteOffset);
+        *lengthPtr = length;
+        return true;
+    }
 
-        Rooted<ArrayBufferObjectMaybeShared*> buffer(cx);
-        if (IsArrayBuffer(bufobj)) {
-            ArrayBufferObject& buf = AsArrayBuffer(bufobj);
-            if (buf.isDetached()) {
-                JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
-                return nullptr;
-            }
-
-            buffer = static_cast<ArrayBufferObjectMaybeShared*>(&buf);
-        } else {
-            buffer = static_cast<ArrayBufferObjectMaybeShared*>(&AsSharedArrayBuffer(bufobj));
-        }
-
-        if (byteOffset > buffer->byteLength() || byteOffset % sizeof(NativeType) != 0) {
+    // ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210
+    // 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] )
+    // Steps 7, 10.a-c, 11.b-c.
+    static bool
+    computeAndCheckLength(JSContext* cx, uint32_t bufferByteLength, uint32_t byteOffset,
+                          int32_t lengthInt, uint32_t* length)
+    {
+        // Steps 7, 10.c, 11.c.
+        if (byteOffset > bufferByteLength || byteOffset % sizeof(NativeType) != 0) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                       JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
-            return nullptr; // invalid byteOffset
+            return false; // invalid byteOffset
         }
 
         uint32_t len;
-        if (lengthInt == -1) {
-            len = (buffer->byteLength() - byteOffset) / sizeof(NativeType);
-            if (len * sizeof(NativeType) != buffer->byteLength() - byteOffset) {
+        if (lengthInt < 0) {
+            // Step 10.b.
+            uint32_t newByteLength = bufferByteLength - byteOffset;
+            len = newByteLength / sizeof(NativeType);
+
+            // Step 10.a.
+            if (len * sizeof(NativeType) != newByteLength) {
                 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                           JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
-                return nullptr; // given byte array doesn't map exactly to sizeof(NativeType) * N
+                return false; // given byte array doesn't map exactly to sizeof(NativeType) * N
             }
         } else {
+            // Step 11.b (implicit).
             len = uint32_t(lengthInt);
         }
 
+        // Step 11.c.
         // Go slowly and check for overflow.
         uint32_t arrayByteLength = len * sizeof(NativeType);
         if (len >= INT32_MAX / sizeof(NativeType) || byteOffset >= INT32_MAX - arrayByteLength) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                       JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
-            return nullptr; // overflow when calculating byteOffset + len * sizeof(NativeType)
+            return false; // overflow when calculating byteOffset + len * sizeof(NativeType)
+        }
+
+        // Step 11.c.
+        if (arrayByteLength + byteOffset > bufferByteLength) {
+            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+                                      JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
+            return false; // byteOffset + len is too big for the arraybuffer
         }
 
-        if (arrayByteLength + byteOffset > buffer->byteLength()) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                                      JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
-            return nullptr; // byteOffset + len is too big for the arraybuffer
+        *length = len;
+        return true;
+    }
+
+    static JSObject*
+    fromBufferSameCompartment(JSContext* cx,
+                              HandleArrayBufferObjectMaybeShared bufferMaybeDetached,
+                              uint32_t bufferByteLength, 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 (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);
+    }
+
+    // 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
+    // compartment A backed by an ArrayBuffer in compartment B. So in this
+    // 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,
+                      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);
+            return nullptr;
+        }
+        MOZ_ASSERT(unwrappedBufferMaybeDetached->byteLength() == bufferByteLength);
+
+        // 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;
         }
 
-        return makeInstance(cx, buffer, byteOffset, len, proto);
+        RootedObject typedArray(cx);
+        {
+            JSAutoCompartment ac(cx, unwrappedBufferMaybeDetached);
+
+            RootedObject wrappedProto(cx, protoRoot);
+            if (!cx->compartment()->wrap(cx, &wrappedProto))
+                return nullptr;
+
+            typedArray =
+                makeInstance(cx, unwrappedBufferMaybeDetached, 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 (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);
+        }
+
+        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);
     }
 
     static bool
     maybeCreateArrayBuffer(JSContext* cx, uint32_t count, uint32_t unit,
                            HandleObject nonDefaultProto,
                            MutableHandle<ArrayBufferObject*> buffer)
     {
         if (count >= INT32_MAX / unit) {
@@ -1774,48 +1877,16 @@ TypedArrayObject::sharedTypedArrayProtot
     // draft 20140824 requires.)  But this is about as much as we can do
     // until we implement @@toStringTag.
     "???",
     JSCLASS_HAS_CACHED_PROTO(JSProto_TypedArray),
     JS_NULL_CLASS_OPS,
     &TypedArrayObjectSharedTypedArrayPrototypeClassSpec
 };
 
-template<typename T>
-bool
-ArrayBufferObject::createTypedArrayFromBufferImpl(JSContext* cx, const CallArgs& args)
-{
-    typedef TypedArrayObjectTemplate<T> ArrayType;
-    MOZ_ASSERT(IsAnyArrayBuffer(args.thisv()));
-    MOZ_ASSERT(args.length() == 3);
-
-    Rooted<JSObject*> buffer(cx, &args.thisv().toObject());
-    Rooted<JSObject*> proto(cx, &args[2].toObject());
-
-    Rooted<JSObject*> obj(cx);
-    double byteOffset = args[0].toNumber();
-    MOZ_ASSERT(0 <= byteOffset);
-    MOZ_ASSERT(byteOffset <= UINT32_MAX);
-    MOZ_ASSERT(byteOffset == uint32_t(byteOffset));
-    obj = ArrayType::fromBufferWithProto(cx, buffer, uint32_t(byteOffset), args[1].toInt32(),
-                                         proto);
-    if (!obj)
-        return false;
-    args.rval().setObject(*obj);
-    return true;
-}
-
-template<typename T>
-bool
-ArrayBufferObject::createTypedArrayFromBuffer(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsAnyArrayBuffer, createTypedArrayFromBufferImpl<T> >(cx, args);
-}
-
 // this default implementation is only valid for integer types
 // less than 32-bits in size.
 template<typename NativeType>
 Value
 TypedArrayObjectTemplate<NativeType>::getIndexValue(JSObject* tarray, uint32_t index)
 {
     static_assert(sizeof(NativeType) < 4,
                   "this method must only handle NativeType values that are "
--- a/js/src/vm/TypedArrayObject.h
+++ b/js/src/vm/TypedArrayObject.h
@@ -422,33 +422,16 @@ TypedArrayElemSize(Scalar::Type viewType
 //   target[targetOffset + unsafeSrcCrossCompartment.length - 1] =
 //       unsafeSrcCrossCompartment[unsafeSrcCrossCompartment.length - 1]
 //
 // where the source element range doesn't overlap the target element range in
 // memory.
 extern void
 SetDisjointTypedElements(TypedArrayObject* target, uint32_t targetOffset,
                          TypedArrayObject* unsafeSrcCrossCompartment);
-static inline bool
-IsAnyArrayBuffer(HandleObject obj)
-{
-    return IsArrayBuffer(obj) || IsSharedArrayBuffer(obj);
-}
-
-static inline bool
-IsAnyArrayBuffer(JSObject* obj)
-{
-    return IsArrayBuffer(obj) || IsSharedArrayBuffer(obj);
-}
-
-static inline bool
-IsAnyArrayBuffer(HandleValue v)
-{
-    return v.isObject() && IsAnyArrayBuffer(&v.toObject());
-}
 
 } // namespace js
 
 template <>
 inline bool
 JSObject::is<js::TypedArrayObject>() const
 {
     return js::IsTypedArrayClass(getClass());