Bug 1686445 part 2 - Add structured clone support for large ArrayBuffers. r=sfink
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 14 Jan 2021 21:36:30 +0000
changeset 563256 fae906026e24e59efa39e781932fc5c654d3ffe6
parent 563255 eae09146c9f9215a7c843544104319cb9b93fa38
child 563257 ad251c4f6d76433404649f6bad849e257e28e434
push id38108
push userncsoregi@mozilla.com
push dateFri, 15 Jan 2021 21:53:19 +0000
treeherdermozilla-central@0695ec653f99 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink
bugs1686445
milestone86.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 1686445 part 2 - Add structured clone support for large ArrayBuffers. r=sfink Renames SCTAG_ARRAY_BUFFER_OBJECT to SCTAG_ARRAY_BUFFER_OBJECT_V2 and adds a new SCTAG_ARRAY_BUFFER_OBJECT that encodes the length as 64-bit value. Differential Revision: https://phabricator.services.mozilla.com/D101735
js/src/jit-test/tests/structured-clone/array-buffers.js
js/src/vm/StructuredClone.cpp
--- a/js/src/jit-test/tests/structured-clone/array-buffers.js
+++ b/js/src/jit-test/tests/structured-clone/array-buffers.js
@@ -32,8 +32,17 @@ testV2DataView();
 function testV2ArrayBuffer() {
     var buf = new Uint8Array([3,0,0,0,0,0,241,255,4,0,0,0,9,0,255,255,33,44,55,66,0,0,0,0]);
     clonebuffer.clonebuffer = buf.buffer;
     var ab = deserialize(clonebuffer);
     assertEq(ab instanceof ArrayBuffer, true);
     assertEq(new Uint8Array(ab).toString(), "33,44,55,66");
 }
 testV2ArrayBuffer();
+
+function testArrayBuffer() {
+    var ta = new Uint8Array([33, 44, 55, 66]);
+    var clonebuf = serialize(ta.buffer, undefined, {scope: "DifferentProcessForIndexedDB"});
+    var ab = deserialize(clonebuf);
+    assertEq(ab instanceof ArrayBuffer, true);
+    assertEq(new Uint8Array(ab).toString(), "33,44,55,66");
+}
+testArrayBuffer();
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -100,17 +100,17 @@ enum StructuredDataType : uint32_t {
   SCTAG_UNDEFINED,
   SCTAG_BOOLEAN,
   SCTAG_INT32,
   SCTAG_STRING,
   SCTAG_DATE_OBJECT,
   SCTAG_REGEXP_OBJECT,
   SCTAG_ARRAY_OBJECT,
   SCTAG_OBJECT_OBJECT,
-  SCTAG_ARRAY_BUFFER_OBJECT,
+  SCTAG_ARRAY_BUFFER_OBJECT_V2,  // Old version, for backwards compatibility.
   SCTAG_BOOLEAN_OBJECT,
   SCTAG_STRING_OBJECT,
   SCTAG_NUMBER_OBJECT,
   SCTAG_BACK_REFERENCE_OBJECT,
   SCTAG_DO_NOT_USE_1,  // Required for backwards compatibility
   SCTAG_DO_NOT_USE_2,  // Required for backwards compatibility
   SCTAG_TYPED_ARRAY_OBJECT,
   SCTAG_MAP_OBJECT,
@@ -127,16 +127,18 @@ enum StructuredDataType : uint32_t {
   SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM,
 
   SCTAG_SHARED_ARRAY_BUFFER_OBJECT,
   SCTAG_SHARED_WASM_MEMORY_OBJECT,
 
   SCTAG_BIGINT,
   SCTAG_BIGINT_OBJECT,
 
+  SCTAG_ARRAY_BUFFER_OBJECT,
+
   SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100,
   SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int8,
   SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8,
   SCTAG_TYPED_ARRAY_V1_INT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int16,
   SCTAG_TYPED_ARRAY_V1_UINT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint16,
   SCTAG_TYPED_ARRAY_V1_INT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int32,
   SCTAG_TYPED_ARRAY_V1_UINT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint32,
   SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float32,
@@ -423,17 +425,18 @@ struct JSStructuredCloneReader {
   JSString* readStringImpl(uint32_t nchars, gc::InitialHeap heap);
   JSString* readString(uint32_t data, gc::InitialHeap heap = gc::DefaultHeap);
 
   BigInt* readBigInt(uint32_t data);
 
   MOZ_MUST_USE bool readTypedArray(uint32_t arrayType, uint32_t nelems,
                                    MutableHandleValue vp, bool v1Read = false);
   MOZ_MUST_USE bool readDataView(uint32_t byteLength, MutableHandleValue vp);
-  MOZ_MUST_USE bool readArrayBuffer(uint32_t nbytes, MutableHandleValue vp);
+  MOZ_MUST_USE bool readArrayBuffer(StructuredDataType type, uint32_t data,
+                                    MutableHandleValue vp);
   MOZ_MUST_USE bool readSharedArrayBuffer(MutableHandleValue vp);
   MOZ_MUST_USE bool readSharedWasmMemory(uint32_t nbytes,
                                          MutableHandleValue vp);
   MOZ_MUST_USE bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems,
                                       MutableHandleValue vp);
   JSObject* readSavedFrame(uint32_t principalsTag);
   MOZ_MUST_USE bool startRead(MutableHandleValue vp,
                               gc::InitialHeap strHeap = gc::DefaultHeap);
@@ -1300,19 +1303,26 @@ bool JSStructuredCloneWriter::writeDataV
   return out.write(view->byteOffset().deprecatedGetUint32());
 }
 
 bool JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj) {
   Rooted<ArrayBufferObject*> buffer(context(),
                                     obj->maybeUnwrapAs<ArrayBufferObject>());
   JSAutoRealm ar(context(), buffer);
 
-  size_t byteLength = buffer->byteLength().deprecatedGetUint32();
-  return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, byteLength) &&
-         out.writeBytes(buffer->dataPointer(), byteLength);
+  if (!out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, 0)) {
+    return false;
+  }
+
+  uint64_t byteLength = buffer->byteLength().get();
+  if (!out.write(byteLength)) {
+    return false;
+  }
+
+  return out.writeBytes(buffer->dataPointer(), byteLength);
 }
 
 bool JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj) {
   MOZ_ASSERT(obj->canUnwrapAs<SharedArrayBufferObject>());
 
   if (!cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
     auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
                      ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
@@ -2309,26 +2319,47 @@ bool JSStructuredCloneReader::readDataVi
   }
   vp.setObject(*obj);
 
   allObjs[placeholderIndex].set(vp);
 
   return true;
 }
 
-bool JSStructuredCloneReader::readArrayBuffer(uint32_t nbytes,
+bool JSStructuredCloneReader::readArrayBuffer(StructuredDataType type,
+                                              uint32_t data,
                                               MutableHandleValue vp) {
+  // V2 stores the length in |data|. The current version stores the
+  // length separately to allow larger length values.
+  uint64_t nbytes = 0;
+  if (type == SCTAG_ARRAY_BUFFER_OBJECT) {
+    if (!in.read(&nbytes)) {
+      return false;
+    }
+  } else {
+    MOZ_ASSERT(type == SCTAG_ARRAY_BUFFER_OBJECT_V2);
+    nbytes = data;
+  }
+
+  // The maximum ArrayBuffer size depends on the platform and prefs, and we cast
+  // to BufferSize/size_t below, so we have to check this here.
+  if (nbytes > ArrayBufferObject::maxBufferByteLength()) {
+    JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
+                              JSMSG_BAD_ARRAY_LENGTH);
+    return false;
+  }
+
   JSObject* obj =
       ArrayBufferObject::createZeroed(context(), BufferSize(nbytes));
   if (!obj) {
     return false;
   }
   vp.setObject(*obj);
   ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
-  MOZ_ASSERT(buffer.byteLength().deprecatedGetUint32() == nbytes);
+  MOZ_ASSERT(buffer.byteLength().get() == nbytes);
   return in.readArray(buffer.dataPointer(), nbytes);
 }
 
 bool JSStructuredCloneReader::readSharedArrayBuffer(MutableHandleValue vp) {
   if (!cloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed() ||
       !cloneDataPolicy.areSharedMemoryObjectsAllowed()) {
     auto error = context()->realm()->creationOptions().getCoopAndCoepEnabled()
                      ? JS_SCERR_NOT_CLONABLE_WITH_COOP_COEP
@@ -2653,18 +2684,19 @@ bool JSStructuredCloneReader::startRead(
 
     case SCTAG_TRANSFER_MAP_HEADER:
     case SCTAG_TRANSFER_MAP_PENDING_ENTRY:
       // We should be past all the transfer map tags.
       JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr,
                                 JSMSG_SC_BAD_SERIALIZED_DATA, "invalid input");
       return false;
 
+    case SCTAG_ARRAY_BUFFER_OBJECT_V2:
     case SCTAG_ARRAY_BUFFER_OBJECT:
-      if (!readArrayBuffer(data, vp)) {
+      if (!readArrayBuffer(StructuredDataType(tag), data, vp)) {
         return false;
       }
       break;
 
     case SCTAG_SHARED_ARRAY_BUFFER_OBJECT:
       if (!readSharedArrayBuffer(vp)) {
         return false;
       }
@@ -2880,22 +2912,23 @@ bool JSStructuredCloneReader::readTransf
       if (!in.seekBy(static_cast<size_t>(extraData))) {
         return false;
       }
 
       uint32_t tag, data;
       if (!in.readPair(&tag, &data)) {
         return false;
       }
-      if (tag != SCTAG_ARRAY_BUFFER_OBJECT) {
+      if (tag != SCTAG_ARRAY_BUFFER_OBJECT_V2 &&
+          tag != SCTAG_ARRAY_BUFFER_OBJECT) {
         ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
         return false;
       }
       RootedValue val(cx);
-      if (!readArrayBuffer(data, &val)) {
+      if (!readArrayBuffer(StructuredDataType(tag), data, &val)) {
         return false;
       }
       obj = &val.toObject();
     } else {
       if (!callbacks || !callbacks->readTransfer) {
         ReportDataCloneError(cx, callbacks, JS_SCERR_TRANSFERABLE, closure);
         return false;
       }