Bug 1412852 - Structured clone WebAssembly.Memory objects. r=sfink
authorLars T Hansen <lhansen@mozilla.com>
Tue, 31 Oct 2017 13:38:28 +0100
changeset 701932 e8fb22afd5aaad1bd5b4b36725e4856b4557843e
parent 701931 ff11b23cf898b48f6871bf0746cb150a5634143e
child 701933 a2f237ccf5bf2ac923bbf8d2cb1b175a50e3f532
push id90308
push userbmo:lhansen@mozilla.com
push dateWed, 22 Nov 2017 12:45:04 +0000
reviewerssfink
bugs1412852
milestone59.0a1
Bug 1412852 - Structured clone WebAssembly.Memory objects. r=sfink
js/public/StructuredClone.h
js/src/js.msg
js/src/vm/StructuredClone.cpp
js/src/wasm/WasmJS.cpp
js/src/wasm/WasmJS.h
--- a/js/public/StructuredClone.h
+++ b/js/public/StructuredClone.h
@@ -359,17 +359,17 @@ class JS_PUBLIC_API(JSAutoStructuredClon
 // The range of tag values the application may use for its own custom object types.
 #define JS_SCTAG_USER_MIN  ((uint32_t) 0xFFFF8000)
 #define JS_SCTAG_USER_MAX  ((uint32_t) 0xFFFFFFFF)
 
 #define JS_SCERR_RECURSION 0
 #define JS_SCERR_TRANSFERABLE 1
 #define JS_SCERR_DUP_TRANSFERABLE 2
 #define JS_SCERR_UNSUPPORTED_TYPE 3
-#define JS_SCERR_SAB_TRANSFERABLE 4
+#define JS_SCERR_SHMEM_TRANSFERABLE 4
 
 JS_PUBLIC_API(bool)
 JS_ReadUint32Pair(JSStructuredCloneReader* r, uint32_t* p1, uint32_t* p2);
 
 JS_PUBLIC_API(bool)
 JS_ReadBytes(JSStructuredCloneReader* r, void* p, size_t len);
 
 JS_PUBLIC_API(bool)
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -430,19 +430,19 @@ MSG_DEF(JSMSG_BAD_TRAP,                1
 
 // Structured cloning
 MSG_DEF(JSMSG_SC_BAD_CLONE_VERSION,    0, JSEXN_ERR, "unsupported structured clone version")
 MSG_DEF(JSMSG_SC_BAD_SERIALIZED_DATA,  1, JSEXN_INTERNALERR, "bad serialized structured data ({0})")
 MSG_DEF(JSMSG_SC_DUP_TRANSFERABLE,     0, JSEXN_TYPEERR, "duplicate transferable for structured clone")
 MSG_DEF(JSMSG_SC_NOT_TRANSFERABLE,     0, JSEXN_TYPEERR, "invalid transferable array for structured clone")
 MSG_DEF(JSMSG_SC_UNSUPPORTED_TYPE,     0, JSEXN_TYPEERR, "unsupported type for structured data")
 MSG_DEF(JSMSG_SC_NOT_CLONABLE,         1, JSEXN_TYPEERR, "{0} cannot be cloned in this context")
-MSG_DEF(JSMSG_SC_SAB_TRANSFERABLE,     0, JSEXN_TYPEERR, "SharedArrayBuffer must not be in the transfer list")
 MSG_DEF(JSMSG_SC_SAB_DISABLED,         0, JSEXN_TYPEERR, "SharedArrayBuffer not cloned - shared memory disabled in receiver")
 MSG_DEF(JSMSG_SC_SAB_REFCNT_OFLO,      0, JSEXN_TYPEERR, "SharedArrayBuffer has too many references")
+MSG_DEF(JSMSG_SC_SHMEM_TRANSFERABLE,   0, JSEXN_TYPEERR, "Shared memory objects must not be in the transfer list")
 
 // Debugger
 MSG_DEF(JSMSG_ASSIGN_FUNCTION_OR_NULL, 1, JSEXN_TYPEERR, "value assigned to {0} must be a function or null")
 MSG_DEF(JSMSG_DEBUG_BAD_LINE,          0, JSEXN_TYPEERR, "invalid line number")
 MSG_DEF(JSMSG_DEBUG_BAD_OFFSET,        0, JSEXN_TYPEERR, "invalid script offset")
 MSG_DEF(JSMSG_DEBUG_BAD_REFERENT,      2, JSEXN_TYPEERR, "{0} does not refer to {1}")
 MSG_DEF(JSMSG_DEBUG_BAD_RESUMPTION,    0, JSEXN_TYPEERR, "debugger resumption value must be undefined, {throw: val}, {return: val}, or null")
 MSG_DEF(JSMSG_DEBUG_BAD_YIELD,         0, JSEXN_TYPEERR, "generator yielded invalid value")
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -44,16 +44,17 @@
 #include "builtin/MapObject.h"
 #include "js/Date.h"
 #include "js/GCHashTable.h"
 #include "vm/RegExpObject.h"
 #include "vm/SavedFrame.h"
 #include "vm/SharedArrayObject.h"
 #include "vm/TypedArrayObject.h"
 #include "vm/WrapperObject.h"
+#include "wasm/WasmJS.h"
 
 #include "jscntxtinlines.h"
 #include "jsobjinlines.h"
 
 using namespace js;
 
 using mozilla::BitwiseCast;
 using mozilla::IsNaN;
@@ -101,16 +102,17 @@ enum StructuredDataType : uint32_t {
 
     // No new tags before principals.
     SCTAG_JSPRINCIPALS,
     SCTAG_NULL_JSPRINCIPALS,
     SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM,
     SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM,
 
     SCTAG_SHARED_ARRAY_BUFFER_OBJECT,
+    SCTAG_SHARED_WASM_MEMORY_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,
@@ -404,16 +406,17 @@ struct JSStructuredCloneReader {
     JSString* readString(uint32_t data);
 
     bool checkDouble(double d);
     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 readSharedArrayBuffer(uint32_t nbytes, 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);
 
     SCInput& in;
 
     // The widest scope that the caller will accept, where
     // SameProcessSameThread is the widest (it can store anything it wants) and
@@ -496,16 +499,17 @@ struct JSStructuredCloneWriter {
     bool writeHeader();
     bool writeTransferMap();
 
     bool writeString(uint32_t tag, JSString* str);
     bool writeArrayBuffer(HandleObject obj);
     bool writeTypedArray(HandleObject obj);
     bool writeDataView(HandleObject obj);
     bool writeSharedArrayBuffer(HandleObject obj);
+    bool writeSharedWasmMemory(HandleObject obj);
     bool startObject(HandleObject obj, bool* backref);
     bool startWrite(HandleValue v);
     bool traverseObject(HandleObject obj);
     bool traverseMap(HandleObject obj);
     bool traverseSet(HandleObject obj);
     bool traverseSavedFrame(HandleObject obj);
 
     bool reportDataCloneError(uint32_t errorId);
@@ -594,18 +598,18 @@ ReportDataCloneError(JSContext* cx,
       case JS_SCERR_TRANSFERABLE:
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_NOT_TRANSFERABLE);
         break;
 
       case JS_SCERR_UNSUPPORTED_TYPE:
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_UNSUPPORTED_TYPE);
         break;
 
-      case JS_SCERR_SAB_TRANSFERABLE:
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_SAB_TRANSFERABLE);
+      case JS_SCERR_SHMEM_TRANSFERABLE:
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SC_SHMEM_TRANSFERABLE);
         break;
 
       default:
         MOZ_CRASH("Unkown errorId");
         break;
     }
 }
 
@@ -1092,18 +1096,25 @@ JSStructuredCloneWriter::parseTransferab
 
         if (!JS_GetElement(cx, array, i, &v))
             return false;
 
         if (!v.isObject())
             return reportDataCloneError(JS_SCERR_TRANSFERABLE);
         tObj = &v.toObject();
 
+        // Shared memory cannot be transferred because it is not possible (nor
+        // desirable) to detach the memory in agents that already hold a
+        // reference to it.
+
         if (tObj->is<SharedArrayBufferObject>())
-            return reportDataCloneError(JS_SCERR_SAB_TRANSFERABLE);
+            return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE);
+
+        if (tObj->is<WasmMemoryObject>() && tObj->as<WasmMemoryObject>().isShared())
+            return reportDataCloneError(JS_SCERR_SHMEM_TRANSFERABLE);
 
         // No duplicates allowed
         auto p = transferableObjects.lookupForAdd(tObj);
         if (p)
             return reportDataCloneError(JS_SCERR_DUP_TRANSFERABLE);
 
         if (!transferableObjects.add(p, tObj))
             return false;
@@ -1223,16 +1234,18 @@ JSStructuredCloneWriter::writeArrayBuffe
 
     return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, buffer->byteLength()) &&
            out.writeBytes(buffer->dataPointer(), buffer->byteLength());
 }
 
 bool
 JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj)
 {
+    MOZ_ASSERT(CheckedUnwrap(obj) && CheckedUnwrap(obj)->is<SharedArrayBufferObject>());
+
     if (!cloneDataPolicy.isSharedArrayBufferAllowed()) {
         JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_NOT_CLONABLE,
                                   "SharedArrayBuffer");
         return false;
     }
 
     // We must not transfer buffer pointers cross-process.  The cloneDataPolicy
     // should guard against this; check that it does.
@@ -1240,22 +1253,50 @@ JSStructuredCloneWriter::writeSharedArra
     MOZ_RELEASE_ASSERT(scope <= JS::StructuredCloneScope::SameProcessDifferentThread);
 
     Rooted<SharedArrayBufferObject*> sharedArrayBuffer(context(), &CheckedUnwrap(obj)->as<SharedArrayBufferObject>());
     SharedArrayRawBuffer* rawbuf = sharedArrayBuffer->rawBufferObject();
 
     if (!refsHeld.acquire(context(), rawbuf))
         return false;
 
+    // We must serialize the length so that the buffer object arrives in the
+    // receiver with the same length, and not with the length read from the
+    // rawbuf - that length can be different, and it can change at any time.
+
     intptr_t p = reinterpret_cast<intptr_t>(rawbuf);
+    uint32_t byteLength = sharedArrayBuffer->byteLength();
     return out.writePair(SCTAG_SHARED_ARRAY_BUFFER_OBJECT, static_cast<uint32_t>(sizeof(p))) &&
+           out.writeBytes(&byteLength, sizeof(byteLength)) &&
            out.writeBytes(&p, sizeof(p));
 }
 
 bool
+JSStructuredCloneWriter::writeSharedWasmMemory(HandleObject obj)
+{
+    MOZ_ASSERT(CheckedUnwrap(obj) && CheckedUnwrap(obj)->is<WasmMemoryObject>());
+
+    // Check the policy here so that we can report a sane error.
+    if (!cloneDataPolicy.isSharedArrayBufferAllowed()) {
+        JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_NOT_CLONABLE,
+                                  "WebAssembly.Memory");
+        return false;
+    }
+
+    // If this changes, might need to change what we write.
+    MOZ_ASSERT(WasmMemoryObject::RESERVED_SLOTS == 2);
+
+    Rooted<WasmMemoryObject*> memoryObj(context(), &CheckedUnwrap(obj)->as<WasmMemoryObject>());
+    Rooted<SharedArrayBufferObject*> sab(context(), &memoryObj->buffer().as<SharedArrayBufferObject>());
+
+    return out.writePair(SCTAG_SHARED_WASM_MEMORY_OBJECT, 0) &&
+           writeSharedArrayBuffer(sab);
+}
+
+bool
 JSStructuredCloneWriter::startObject(HandleObject obj, bool* backref)
 {
     /* Handle cycles in the object graph. */
     CloneMemory::AddPtr p = memory.lookupForAdd(obj);
     if ((*backref = p.found()))
         return out.writePair(SCTAG_BACK_REFERENCE_OBJECT, p->value());
     if (!memory.add(p, obj, memory.count())) {
         ReportOutOfMemory(context());
@@ -1508,16 +1549,18 @@ JSStructuredCloneWriter::startWrite(Hand
         } else if (JS_IsTypedArrayObject(obj)) {
             return writeTypedArray(obj);
         } else if (JS_IsDataViewObject(obj)) {
             return writeDataView(obj);
         } else if (JS_IsArrayBufferObject(obj) && JS_ArrayBufferHasData(obj)) {
             return writeArrayBuffer(obj);
         } else if (JS_IsSharedArrayBufferObject(obj)) {
             return writeSharedArrayBuffer(obj);
+        } else if (wasm::IsSharedWasmMemoryObject(obj)) {
+            return writeSharedWasmMemory(obj);
         } else if (cls == ESClass::Object) {
             return traverseObject(obj);
         } else if (cls == ESClass::Array) {
             return traverseObject(obj);
         } else if (cls == ESClass::Boolean) {
             RootedValue unboxed(context());
             if (!Unbox(context(), obj, &unboxed))
                 return false;
@@ -1958,16 +2001,20 @@ JSStructuredCloneReader::readArrayBuffer
     ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
     MOZ_ASSERT(buffer.byteLength() == nbytes);
     return in.readArray(buffer.dataPointer(), nbytes);
 }
 
 bool
 JSStructuredCloneReader::readSharedArrayBuffer(uint32_t nbytes, MutableHandleValue vp)
 {
+    uint32_t byteLength;
+    if (!in.readBytes(&byteLength, sizeof(byteLength)))
+        return in.reportTruncated();
+
     intptr_t p;
     if (!in.readBytes(&p, sizeof(p)))
         return in.reportTruncated();
 
     SharedArrayRawBuffer* rawbuf = reinterpret_cast<SharedArrayRawBuffer*>(p);
 
     // There's no guarantee that the receiving agent has enabled shared memory
     // even if the transmitting agent has done so.  Ideally we'd check at the
@@ -1986,27 +2033,51 @@ JSStructuredCloneReader::readSharedArray
 
     // The new object will have a new reference to the rawbuf.
 
     if (!rawbuf->addReference()) {
         JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_SAB_REFCNT_OFLO);
         return false;
     }
 
-    SharedArrayRawBuffer::Lock l(rawbuf);
-    JSObject* obj = SharedArrayBufferObject::New(context(), rawbuf, rawbuf->byteLength(l));
+    JSObject* obj = SharedArrayBufferObject::New(context(), rawbuf, byteLength);
     if (!obj) {
         rawbuf->dropReference();
         return false;
     }
 
     vp.setObject(*obj);
     return true;
 }
 
+bool
+JSStructuredCloneReader::readSharedWasmMemory(uint32_t nbytes, MutableHandleValue vp)
+{
+    MOZ_ASSERT(nbytes == 0);
+
+    JSContext* cx = context();
+
+    // Read the SharedArrayBuffer object.
+    RootedValue payload(cx);
+    if (!startRead(&payload))
+        return false;
+
+    Rooted<ArrayBufferObjectMaybeShared*> sab(
+        cx, &payload.toObject().as<SharedArrayBufferObject>());
+
+    // Construct the memory.
+    RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmMemory).toObject());
+    RootedObject memory(cx, WasmMemoryObject::create(cx, sab, proto));
+    if (!memory)
+        return false;
+
+    vp.setObject(*memory);
+    return true;
+}
+
 /*
  * Read in the data for a structured clone version 1 ArrayBuffer, performing
  * endianness-conversion while reading.
  */
 bool
 JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems,
                                            MutableHandleValue vp)
 {
@@ -2178,16 +2249,21 @@ JSStructuredCloneReader::startRead(Mutab
             return false;
         break;
 
       case SCTAG_SHARED_ARRAY_BUFFER_OBJECT:
         if (!readSharedArrayBuffer(data, vp))
             return false;
         break;
 
+      case SCTAG_SHARED_WASM_MEMORY_OBJECT:
+        if (!readSharedWasmMemory(data, vp))
+            return false;
+        break;
+
       case SCTAG_TYPED_ARRAY_OBJECT: {
         // readTypedArray adds the array to allObjs.
         uint64_t arrayType;
         if (!in.read(&arrayType))
             return false;
         return readTypedArray(arrayType, data, vp);
       }
 
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -1553,16 +1553,23 @@ WasmMemoryObject::grow(HandleWasmMemoryO
         MOZ_ASSERT(prevMemoryBase);
         for (InstanceSet::Range r = memory->observers().all(); !r.empty(); r.popFront())
             r.front()->instance().onMovingGrowMemory(prevMemoryBase);
     }
 
     return oldNumPages;
 }
 
+bool
+js::wasm::IsSharedWasmMemoryObject(JSObject* obj)
+{
+    obj = CheckedUnwrap(obj);
+    return obj && obj->is<WasmMemoryObject>() && obj->as<WasmMemoryObject>().isShared();
+}
+
 // ============================================================================
 // WebAssembly.Table class and methods
 
 const ClassOps WasmTableObject::classOps_ =
 {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
     nullptr, /* enumerate */
--- a/js/src/wasm/WasmJS.h
+++ b/js/src/wasm/WasmJS.h
@@ -66,16 +66,19 @@ extern Instance&
 ExportedFunctionToInstance(JSFunction* fun);
 
 extern WasmInstanceObject*
 ExportedFunctionToInstanceObject(JSFunction* fun);
 
 extern uint32_t
 ExportedFunctionToFuncIndex(JSFunction* fun);
 
+extern bool
+IsSharedWasmMemoryObject(JSObject* obj);
+
 } // namespace wasm
 
 // The class of the WebAssembly global namespace object.
 
 extern const Class WebAssemblyClass;
 
 JSObject*
 InitWebAssemblyClass(JSContext* cx, HandleObject global);