Bug 1050340 - Handle ArrayBuffers and TypedArrays pseudo-generically. r=luke
authorBobby Holley <bobbyholley@gmail.com>
Mon, 18 Aug 2014 14:18:39 -0700
changeset 200208 f752615f4e19b83ce3d870a45163ab2d263eedba
parent 200207 a4014b7b418a4144eab90bdebadee40e3bac2563
child 200209 7d1e2bb43a8c1aae2a2c450865cf9b0168bcb295
push id27337
push useremorley@mozilla.com
push dateTue, 19 Aug 2014 12:40:34 +0000
treeherdermozilla-central@a38daccaa557 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1050340
milestone34.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 1050340 - Handle ArrayBuffers and TypedArrays pseudo-generically. r=luke
js/src/jsfriendapi.h
js/src/vm/ArrayBufferObject.cpp
js/src/vm/StructuredClone.cpp
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1725,16 +1725,27 @@ JS_IsArrayBufferObject(JSObject *obj);
  * |obj| must have passed a JS_IsArrayBufferObject test, or somehow be known
  * that it would pass such a test: it is an ArrayBuffer or a wrapper of an
  * ArrayBuffer, and the unwrapping will succeed.
  */
 extern JS_FRIEND_API(uint32_t)
 JS_GetArrayBufferByteLength(JSObject *obj);
 
 /*
+ * Return true if the arrayBuffer contains any data. This will return false for
+ * ArrayBuffer.prototype and neutered ArrayBuffers.
+ *
+ * |obj| must have passed a JS_IsArrayBufferObject test, or somehow be known
+ * that it would pass such a test: it is an ArrayBuffer or a wrapper of an
+ * ArrayBuffer, and the unwrapping will succeed.
+ */
+extern JS_FRIEND_API(bool)
+JS_ArrayBufferHasData(JSObject *obj);
+
+/*
  * Check whether the obj is ArrayBufferObject and memory mapped. Note that this
  * may return false if a security wrapper is encountered that denies the
  * unwrapping.
  */
 extern JS_FRIEND_API(bool)
 JS_IsMappedArrayBufferObject(JSObject *obj);
 
 /*
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -1142,16 +1142,22 @@ JS_NewArrayBufferWithContents(JSContext 
 
 JS_FRIEND_API(bool)
 JS_IsArrayBufferObject(JSObject *obj)
 {
     obj = CheckedUnwrap(obj);
     return obj ? obj->is<ArrayBufferObject>() : false;
 }
 
+JS_FRIEND_API(bool)
+JS_ArrayBufferHasData(JSObject *obj)
+{
+    return CheckedUnwrap(obj)->as<ArrayBufferObject>().hasData();
+}
+
 JS_FRIEND_API(JSObject *)
 js::UnwrapArrayBuffer(JSObject *obj)
 {
     if (JSObject *unwrapped = CheckedUnwrap(obj))
         return unwrapped->is<ArrayBufferObject>() ? unwrapped : nullptr;
     return nullptr;
 }
 
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -851,17 +851,18 @@ JSStructuredCloneWriter::checkStack()
  * conversion would prevent sharing ArrayBuffers: if you have Int8Array and
  * Int16Array views of the same ArrayBuffer, should the data bytes be
  * byte-swapped when writing or not? The Int8Array requires them to not be
  * swapped; the Int16Array requires that they are.
  */
 bool
 JSStructuredCloneWriter::writeTypedArray(HandleObject obj)
 {
-    Rooted<TypedArrayObject*> tarr(context(), &obj->as<TypedArrayObject>());
+    Rooted<TypedArrayObject*> tarr(context(), &CheckedUnwrap(obj)->as<TypedArrayObject>());
+    JSAutoCompartment ac(context(), tarr);
 
     if (!TypedArrayObject::ensureHasBuffer(context(), tarr))
         return false;
 
     if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, tarr->length()))
         return false;
     uint64_t type = tarr->type();
     if (!out.write(type))
@@ -873,17 +874,18 @@ JSStructuredCloneWriter::writeTypedArray
         return false;
 
     return out.write(tarr->byteOffset());
 }
 
 bool
 JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj)
 {
-    ArrayBufferObject &buffer = obj->as<ArrayBufferObject>();
+    ArrayBufferObject &buffer = CheckedUnwrap(obj)->as<ArrayBufferObject>();
+    JSAutoCompartment ac(context(), &buffer);
 
     return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, buffer.byteLength()) &&
            out.writeBytes(buffer.dataPointer(), buffer.byteLength());
 }
 
 bool
 JSStructuredCloneWriter::startObject(HandleObject obj, bool *backref)
 {
@@ -1015,19 +1017,19 @@ JSStructuredCloneWriter::startWrite(Hand
             RegExpGuard re(context());
             if (!RegExpToShared(context(), obj, &re))
                 return false;
             return out.writePair(SCTAG_REGEXP_OBJECT, re->getFlags()) &&
                    writeString(SCTAG_STRING, re->getSource());
         } else if (ObjectClassIs(obj, ESClass_Date, context())) {
             double d = js_DateGetMsecSinceEpoch(obj);
             return out.writePair(SCTAG_DATE_OBJECT, 0) && out.writeDouble(d);
-        } else if (obj->is<TypedArrayObject>()) {
+        } else if (JS_IsTypedArrayObject(obj)) {
             return writeTypedArray(obj);
-        } else if (obj->is<ArrayBufferObject>() && obj->as<ArrayBufferObject>().hasData()) {
+        } else if (JS_IsArrayBufferObject(obj) && JS_ArrayBufferHasData(obj)) {
             return writeArrayBuffer(obj);
         } else if (obj->is<JSObject>() || obj->is<ArrayObject>()) {
             return traverseObject(obj);
         } else if (obj->is<BooleanObject>()) {
             return out.writePair(SCTAG_BOOLEAN_OBJECT, obj->as<BooleanObject>().unbox());
         } else if (obj->is<NumberObject>()) {
             return out.writePair(SCTAG_NUMBER_OBJECT, 0) &&
                    out.writeDouble(obj->as<NumberObject>().unbox());
@@ -1103,39 +1105,44 @@ JSStructuredCloneWriter::transferOwnersh
         uint64_t extraData;
 
 #if DEBUG
         SCInput::getPair(point, &tag, (uint32_t*) &ownership);
         MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY);
         MOZ_ASSERT(ownership == JS::SCTAG_TMO_UNFILLED);
 #endif
 
-        if (obj->is<ArrayBufferObject>()) {
-            bool isMapped = obj->as<ArrayBufferObject>().isMappedArrayBuffer();
-            size_t nbytes = obj->as<ArrayBufferObject>().byteLength();
-            content = JS_StealArrayBufferContents(context(), obj);
-            if (!content)
-                return false; // Destructor will clean up the already-transferred data
-            tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER;
-            if (isMapped)
-                ownership = JS::SCTAG_TMO_MAPPED_DATA;
-            else
-                ownership = JS::SCTAG_TMO_ALLOC_DATA;
-            extraData = nbytes;
-        } else if (obj->is<SharedArrayBufferObject>()) {
-            SharedArrayRawBuffer *rawbuf = obj->as<SharedArrayBufferObject>().rawBufferObject();
+        if (ObjectClassIs(obj, ESClass_ArrayBuffer, context())) {
+            // The current setup of the array buffer inheritance hierarchy doesn't
+            // lend itself well to generic manipulation via proxies.
+            Rooted<ArrayBufferObject*> arrayBuffer(context(), &CheckedUnwrap(obj)->as<ArrayBufferObject>());
+            if (arrayBuffer->isSharedArrayBuffer()) {
+                SharedArrayRawBuffer *rawbuf = arrayBuffer->as<SharedArrayBufferObject>().rawBufferObject();
+
+                // Avoids a race condition where the parent thread frees the buffer
+                // before the child has accepted the transferable.
+                rawbuf->addReference();
 
-            // Avoids a race condition where the parent thread frees the buffer
-            // before the child has accepted the transferable.
-            rawbuf->addReference();
-
-            tag = SCTAG_TRANSFER_MAP_SHARED_BUFFER;
-            ownership = JS::SCTAG_TMO_SHARED_BUFFER;
-            content = rawbuf;
-            extraData = 0;
+                tag = SCTAG_TRANSFER_MAP_SHARED_BUFFER;
+                ownership = JS::SCTAG_TMO_SHARED_BUFFER;
+                content = rawbuf;
+                extraData = 0;
+            } else {
+                bool isMapped = arrayBuffer->isMappedArrayBuffer();
+                size_t nbytes = arrayBuffer->byteLength();
+                content = JS_StealArrayBufferContents(context(), arrayBuffer);
+                if (!content)
+                    return false; // Destructor will clean up the already-transferred data
+                tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER;
+                if (isMapped)
+                    ownership = JS::SCTAG_TMO_MAPPED_DATA;
+                else
+                    ownership = JS::SCTAG_TMO_ALLOC_DATA;
+                extraData = nbytes;
+            }
         } else {
             if (!callbacks || !callbacks->writeTransfer)
                 return reportErrorTransferable();
             if (!callbacks->writeTransfer(context(), obj, closure, &tag, &ownership, &content, &extraData))
                 return false;
             JS_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY);
         }
 
@@ -2005,19 +2012,10 @@ JS_WriteBytes(JSStructuredCloneWriter *w
 }
 
 JS_PUBLIC_API(bool)
 JS_WriteTypedArray(JSStructuredCloneWriter *w, HandleValue v)
 {
     JS_ASSERT(v.isObject());
     assertSameCompartment(w->context(), v);
     RootedObject obj(w->context(), &v.toObject());
-
-    // If the object is a security wrapper, see if we're allowed to unwrap it.
-    // If we aren't, throw.
-    if (obj->is<WrapperObject>())
-        obj = CheckedUnwrap(obj);
-    if (!obj) {
-        JS_ReportErrorNumber(w->context(), js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED);
-        return false;
-    }
     return w->writeTypedArray(obj);
 }