summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/v8.md251
-rw-r--r--lib/v8.js119
-rw-r--r--node.gyp1
-rw-r--r--src/env.h4
-rw-r--r--src/node_serdes.cc483
-rw-r--r--test/parallel/test-v8-serdes-sharedarraybuffer.js27
-rw-r--r--test/parallel/test-v8-serdes.js120
7 files changed, 1005 insertions, 0 deletions
diff --git a/doc/api/v8.md b/doc/api/v8.md
index 173d0abeef..8d5d428e56 100644
--- a/doc/api/v8.md
+++ b/doc/api/v8.md
@@ -157,3 +157,254 @@ setTimeout(function() { v8.setFlagsFromString('--notrace_gc'); }, 60e3);
[`vm.Script`]: vm.html#vm_new_vm_script_code_options
[here]: https://github.com/thlorenz/v8-flags/blob/master/flags-0.11.md
[`GetHeapSpaceStatistics`]: https://v8docs.nodesource.com/node-5.0/d5/dda/classv8_1_1_isolate.html#ac673576f24fdc7a33378f8f57e1d13a4
+
+## Serialization API
+
+> Stability: 1 - Experimental
+
+The serialization API provides means of serializing JavaScript values in a way
+that is compatible with the [HTML structured clone algorithm][].
+The format is backward-compatible (i.e. safe to store to disk).
+
+*Note*: This API is under development, and changes (including incompatible
+changes to the API or wire format) may occur until this warning is removed.
+
+### v8.serialize(value)
+<!--
+added: REPLACEME
+-->
+
+* Returns: {Buffer}
+
+Uses a [`DefaultSerializer`][] to serialize `value` into a buffer.
+
+### v8.deserialize(buffer)
+<!--
+added: REPLACEME
+-->
+
+* `buffer` {Buffer|Uint8Array} A buffer returned by [`serialize()`][].
+
+Uses a [`DefaultDeserializer`][] with default options to read a JS value
+from a buffer.
+
+### class: v8.Serializer
+<!--
+added: REPLACEME
+-->
+
+#### new Serializer()
+Creates a new `Serializer` object.
+
+#### serializer.writeHeader()
+
+Writes out a header, which includes the serialization format version.
+
+#### serializer.writeValue(value)
+
+Serializes a JavaScript value and adds the serialized representation to the
+internal buffer.
+
+This throws an error if `value` cannot be serialized.
+
+#### serializer.releaseBuffer()
+
+Returns the stored internal buffer. This serializer should not be used once
+the buffer is released. Calling this method results in undefined behavior
+if a previous write has failed.
+
+#### serializer.transferArrayBuffer(id, arrayBuffer)
+
+* `id` {integer} A 32-bit unsigned integer.
+* `arrayBuffer` {ArrayBuffer} An `ArrayBuffer` instance.
+
+Marks an `ArrayBuffer` as havings its contents transferred out of band.
+Pass the corresponding `ArrayBuffer` in the deserializing context to
+[`deserializer.transferArrayBuffer()`][].
+
+#### serializer.writeUint32(value)
+
+* `value` {integer}
+
+Write a raw 32-bit unsigned integer.
+For use inside of a custom [`serializer._writeHostObject()`][].
+
+#### serializer.writeUint64(hi, lo)
+
+* `hi` {integer}
+* `lo` {integer}
+
+Write a raw 64-bit unsigned integer, split into high and low 32-bit parts.
+For use inside of a custom [`serializer._writeHostObject()`][].
+
+#### serializer.writeDouble(value)
+
+* `value` {number}
+
+Write a JS `number` value.
+For use inside of a custom [`serializer._writeHostObject()`][].
+
+#### serializer.writeRawBytes(buffer)
+
+* `buffer` {Buffer|Uint8Array}
+
+Write raw bytes into the serializer’s internal buffer. The deserializer
+will require a way to compute the length of the buffer.
+For use inside of a custom [`serializer._writeHostObject()`][].
+
+#### serializer.\_writeHostObject(object)
+
+* `object` {Object}
+
+This method is called to write some kind of host object, i.e. an object created
+by native C++ bindings. If it is not possible to serialize `object`, a suitable
+exception should be thrown.
+
+This method is not present on the `Serializer` class itself but can be provided
+by subclasses.
+
+#### serializer.\_getDataCloneError(message)
+
+* `message` {string}
+
+This method is called to generate error objects that will be thrown when an
+object can not be cloned.
+
+This method defaults to the [`Error`][] constructor and can be be overridden on
+subclasses.
+
+#### serializer.\_getSharedArrayBufferId(sharedArrayBuffer)
+
+* `sharedArrayBuffer` {SharedArrayBuffer}
+
+This method is called when the serializer is going to serialize a
+`SharedArrayBuffer` object. It must return an unsigned 32-bit integer ID for
+the object, using the same ID if this `SharedArrayBuffer` has already been
+serialized. When deserializing, this ID will be passed to
+[`deserializer.transferArrayBuffer()`][].
+
+If the object cannot be serialized, an exception should be thrown.
+
+This method is not present on the `Serializer` class itself but can be provided
+by subclasses.
+
+#### serializer.\_setTreatArrayBufferViewsAsHostObjects(flag)
+
+* `flag` {boolean}
+
+Indicate whether to treat `TypedArray` and `DataView` objects as
+host objects, i.e. pass them to [`serializer._writeHostObject`][].
+
+The default is not to treat those objects as host objects.
+
+### class: v8.Deserializer
+<!--
+added: REPLACEME
+-->
+
+#### new Deserializer(buffer)
+
+* `buffer` {Buffer|Uint8Array} A buffer returned by [`serializer.releaseBuffer()`][].
+
+Creates a new `Deserializer` object.
+
+#### deserializer.readHeader()
+
+Reads and validates a header (including the format version).
+May, for example, reject an invalid or unsupported wire format. In that case,
+an `Error` is thrown.
+
+#### deserializer.readValue()
+
+Deserializes a JavaScript value from the buffer and returns it.
+
+#### deserializer.transferArrayBuffer(id, arrayBuffer)
+
+* `id` {integer} A 32-bit unsigned integer.
+* `arrayBuffer` {ArrayBuffer|SharedArrayBuffer} An `ArrayBuffer` instance.
+
+Marks an `ArrayBuffer` as havings its contents transferred out of band.
+Pass the corresponding `ArrayBuffer` in the serializing context to
+[`serializer.transferArrayBuffer()`][] (or return the `id` from
+[`serializer._getSharedArrayBufferId()`][] in the case of `SharedArrayBuffer`s).
+
+#### deserializer.getWireFormatVersion()
+
+* Returns: {integer}
+
+Reads the underlying wire format version. Likely mostly to be useful to
+legacy code reading old wire format versions. May not be called before
+`.readHeader()`.
+
+#### deserializer.readUint32()
+
+* Returns: {integer}
+
+Read a raw 32-bit unsigned integer and return it.
+For use inside of a custom [`deserializer._readHostObject()`][].
+
+#### deserializer.readUint64()
+
+* Returns: {Array}
+
+Read a raw 64-bit unsigned integer and return it as an array `[hi, lo]`
+with two 32-bit unsigned integer entries.
+For use inside of a custom [`deserializer._readHostObject()`][].
+
+#### deserializer.readDouble()
+
+* Returns: {number}
+
+Read a JS `number` value.
+For use inside of a custom [`deserializer._readHostObject()`][].
+
+#### deserializer.readRawBytes(length)
+
+* Returns: {Buffer}
+
+Read raw bytes from the deserializer’s internal buffer. The `length` parameter
+must correspond to the length of the buffer that was passed to
+[`serializer.writeRawBytes()`][].
+For use inside of a custom [`deserializer._readHostObject()`][].
+
+#### deserializer.\_readHostObject()
+
+This method is called to read some kind of host object, i.e. an object that is
+created by native C++ bindings. If it is not possible to deserialize the data,
+a suitable exception should be thrown.
+
+This method is not present on the `Deserializer` class itself but can be
+provided by subclasses.
+
+### class: v8.DefaultSerializer
+<!--
+added: REPLACEME
+-->
+
+A subclass of [`Serializer`][] that serializes `TypedArray`
+(in particular [`Buffer`][]) and `DataView` objects as host objects, and only
+stores the part of their underlying `ArrayBuffer`s that they are referring to.
+
+### class: v8.DefaultDeserializer
+<!--
+added: REPLACEME
+-->
+
+A subclass of [`Deserializer`][] corresponding to the format written by
+[`DefaultSerializer`][].
+
+[`Buffer`]: buffer.html
+[`Error`]: errors.html#errors_class_error
+[`deserializer.transferArrayBuffer()`]: #v8_deserializer_transferarraybuffer_id_arraybuffer
+[`deserializer._readHostObject()`]: #v8_deserializer_readhostobject
+[`serializer.transferArrayBuffer()`]: #v8_serializer_transferarraybuffer_id_arraybuffer
+[`serializer.releaseBuffer()`]: #v8_serializer_releasebuffer
+[`serializer.writeRawBytes()`]: #v8_serializer_writerawbytes_buffer
+[`serializer._writeHostObject()`]: #v8_serializer_writehostobject_object
+[`serializer._getSharedArrayBufferId()`]: #v8_serializer_getsharedarraybufferid_sharedarraybuffer
+[`Serializer`]: #v8_class_v8_serializer
+[`Deserializer`]: #v8_class_v8_deserializer
+[`DefaultSerializer`]: #v8_class_v8_defaultserializer
+[`DefaultDeserializer`]: #v8_class_v8_defaultdeserializer
+[`serialize()`]: #v8_v8_serialize_value
+[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
diff --git a/lib/v8.js b/lib/v8.js
index a11baacdc9..7790f9b334 100644
--- a/lib/v8.js
+++ b/lib/v8.js
@@ -14,7 +14,14 @@
'use strict';
+const Buffer = require('buffer').Buffer;
+
const v8binding = process.binding('v8');
+const serdesBinding = process.binding('serdes');
+const bufferBinding = process.binding('buffer');
+
+const { objectToString } = require('internal/util');
+const { FastBuffer } = require('internal/buffer');
// Properties for heap statistics buffer extraction.
const heapStatisticsBuffer =
@@ -80,3 +87,115 @@ exports.getHeapSpaceStatistics = function() {
return heapSpaceStatistics;
};
+
+/* V8 serialization API */
+
+const Serializer = exports.Serializer = serdesBinding.Serializer;
+const Deserializer = exports.Deserializer = serdesBinding.Deserializer;
+
+/* JS methods for the base objects */
+Serializer.prototype._getDataCloneError = Error;
+
+Deserializer.prototype.readRawBytes = function(length) {
+ const offset = this._readRawBytes(length);
+ // `this.buffer` can be a Buffer or a plain Uint8Array, so just calling
+ // `.slice()` doesn't work.
+ return new FastBuffer(this.buffer.buffer,
+ this.buffer.byteOffset + offset,
+ length);
+};
+
+/* Keep track of how to handle different ArrayBufferViews.
+ * The default Serializer for Node does not use the V8 methods for serializing
+ * those objects because Node's `Buffer` objects use pooled allocation in many
+ * cases, and their underlying `ArrayBuffer`s would show up in the
+ * serialization. Because a) those may contain sensitive data and the user
+ * may not be aware of that and b) they are often much larger than the `Buffer`
+ * itself, custom serialization is applied. */
+const arrayBufferViewTypes = [Int8Array, Uint8Array, Uint8ClampedArray,
+ Int16Array, Uint16Array, Int32Array, Uint32Array,
+ Float32Array, Float64Array, DataView];
+
+const arrayBufferViewTypeToIndex = new Map();
+
+{
+ const dummy = new ArrayBuffer();
+ for (const [i, ctor] of arrayBufferViewTypes.entries()) {
+ const tag = objectToString(new ctor(dummy));
+ arrayBufferViewTypeToIndex.set(tag, i);
+ }
+}
+
+const bufferConstructorIndex = arrayBufferViewTypes.push(Buffer) - 1;
+
+class DefaultSerializer extends Serializer {
+ constructor() {
+ super();
+
+ this._setTreatArrayBufferViewsAsHostObjects(true);
+ }
+
+ _writeHostObject(abView) {
+ let i = 0;
+ if (abView.constructor === Buffer) {
+ i = bufferConstructorIndex;
+ } else {
+ const tag = objectToString(abView);
+ i = arrayBufferViewTypeToIndex.get(tag);
+
+ if (i === undefined) {
+ throw this._getDataCloneError(`Unknown host object type: ${tag}`);
+ }
+ }
+ this.writeUint32(i);
+ this.writeUint32(abView.byteLength);
+ this.writeRawBytes(new Uint8Array(abView.buffer,
+ abView.byteOffset,
+ abView.byteLength));
+ }
+}
+
+exports.DefaultSerializer = DefaultSerializer;
+
+class DefaultDeserializer extends Deserializer {
+ constructor(buffer) {
+ super(buffer);
+ }
+
+ _readHostObject() {
+ const typeIndex = this.readUint32();
+ const ctor = arrayBufferViewTypes[typeIndex];
+ const byteLength = this.readUint32();
+ const byteOffset = this._readRawBytes(byteLength);
+ const BYTES_PER_ELEMENT = ctor.BYTES_PER_ELEMENT || 1;
+
+ const offset = this.buffer.byteOffset + byteOffset;
+ if (offset % BYTES_PER_ELEMENT === 0) {
+ return new ctor(this.buffer.buffer,
+ offset,
+ byteLength / BYTES_PER_ELEMENT);
+ } else {
+ // Copy to an aligned buffer first.
+ const copy = Buffer.allocUnsafe(byteLength);
+ bufferBinding.copy(this.buffer, copy, 0, offset, offset + byteLength);
+ return new ctor(copy.buffer,
+ copy.byteOffset,
+ byteLength / BYTES_PER_ELEMENT);
+ }
+ }
+}
+
+exports.DefaultDeserializer = DefaultDeserializer;
+
+exports.serialize = function serialize(value) {
+ const ser = new DefaultSerializer();
+ ser.writeHeader();
+ ser.writeValue(value);
+ return ser.releaseBuffer();
+};
+
+exports.deserialize = function deserialize(buffer) {
+ const der = new DefaultDeserializer(buffer);
+ der.readHeader();
+ return der.readValue();
+};
diff --git a/node.gyp b/node.gyp
index b08bda0dad..597df9d126 100644
--- a/node.gyp
+++ b/node.gyp
@@ -177,6 +177,7 @@
'src/node_main.cc',
'src/node_os.cc',
'src/node_revert.cc',
+ 'src/node_serdes.cc',
'src/node_url.cc',
'src/node_util.cc',
'src/node_v8.cc',
diff --git a/src/env.h b/src/env.h
index abb0e6d0e5..6fd1051a21 100644
--- a/src/env.h
+++ b/src/env.h
@@ -127,6 +127,8 @@ namespace node {
V(fingerprint_string, "fingerprint") \
V(flags_string, "flags") \
V(get_string, "get") \
+ V(get_data_clone_error_string, "_getDataCloneError") \
+ V(get_shared_array_buffer_id_string, "_getSharedArrayBufferId") \
V(gid_string, "gid") \
V(handle_string, "handle") \
V(homedir_string, "homedir") \
@@ -189,6 +191,7 @@ namespace node {
V(priority_string, "priority") \
V(produce_cached_data_string, "produceCachedData") \
V(raw_string, "raw") \
+ V(read_host_object_string, "_readHostObject") \
V(readable_string, "readable") \
V(received_shutdown_string, "receivedShutdown") \
V(refresh_string, "refresh") \
@@ -237,6 +240,7 @@ namespace node {
V(windows_verbatim_arguments_string, "windowsVerbatimArguments") \
V(wrap_string, "wrap") \
V(writable_string, "writable") \
+ V(write_host_object_string, "_writeHostObject") \
V(write_queue_size_string, "writeQueueSize") \
V(x_forwarded_string, "x-forwarded-for") \
V(zero_return_string, "ZERO_RETURN") \
diff --git a/src/node_serdes.cc b/src/node_serdes.cc
new file mode 100644
index 0000000000..144de1695e
--- /dev/null
+++ b/src/node_serdes.cc
@@ -0,0 +1,483 @@
+#include "node.h"
+#include "node_buffer.h"
+#include "base-object.h"
+#include "base-object-inl.h"
+#include "env.h"
+#include "env-inl.h"
+#include "v8.h"
+
+namespace node {
+
+using v8::Array;
+using v8::ArrayBuffer;
+using v8::Context;
+using v8::Function;
+using v8::FunctionCallbackInfo;
+using v8::FunctionTemplate;
+using v8::Integer;
+using v8::Isolate;
+using v8::Just;
+using v8::Local;
+using v8::Maybe;
+using v8::MaybeLocal;
+using v8::Nothing;
+using v8::Object;
+using v8::SharedArrayBuffer;
+using v8::String;
+using v8::Value;
+using v8::ValueDeserializer;
+using v8::ValueSerializer;
+
+class SerializerContext : public BaseObject,
+ public ValueSerializer::Delegate {
+ public:
+ SerializerContext(Environment* env,
+ Local<Object> wrap);
+
+ ~SerializerContext() {}
+
+ void ThrowDataCloneError(Local<String> message) override;
+ Maybe<bool> WriteHostObject(Isolate* isolate, Local<Object> object) override;
+ Maybe<uint32_t> GetSharedArrayBufferId(
+ Isolate* isolate, Local<SharedArrayBuffer> shared_array_buffer) override;
+
+ static void SetTreatArrayBufferViewsAsHostObjects(
+ const FunctionCallbackInfo<Value>& args);
+
+ static void New(const FunctionCallbackInfo<Value>& args);
+ static void WriteHeader(const FunctionCallbackInfo<Value>& args);
+ static void WriteValue(const FunctionCallbackInfo<Value>& args);
+ static void ReleaseBuffer(const FunctionCallbackInfo<Value>& args);
+ static void TransferArrayBuffer(const FunctionCallbackInfo<Value>& args);
+ static void WriteUint32(const FunctionCallbackInfo<Value>& args);
+ static void WriteUint64(const FunctionCallbackInfo<Value>& args);
+ static void WriteDouble(const FunctionCallbackInfo<Value>& args);
+ static void WriteRawBytes(const FunctionCallbackInfo<Value>& args);
+ private:
+ ValueSerializer serializer_;
+};
+
+class DeserializerContext : public BaseObject,
+ public ValueDeserializer::Delegate {
+ public:
+ DeserializerContext(Environment* env,
+ Local<Object> wrap,
+ Local<Value> buffer);
+
+ ~DeserializerContext() {}
+
+ MaybeLocal<Object> ReadHostObject(Isolate* isolate) override;
+
+ static void New(const FunctionCallbackInfo<Value>& args);
+ static void ReadHeader(const FunctionCallbackInfo<Value>& args);
+ static void ReadValue(const FunctionCallbackInfo<Value>& args);
+ static void TransferArrayBuffer(const FunctionCallbackInfo<Value>& args);
+ static void GetWireFormatVersion(const FunctionCallbackInfo<Value>& args);
+ static void ReadUint32(const FunctionCallbackInfo<Value>& args);
+ static void ReadUint64(const FunctionCallbackInfo<Value>& args);
+ static void ReadDouble(const FunctionCallbackInfo<Value>& args);
+ static void ReadRawBytes(const FunctionCallbackInfo<Value>& args);
+ private:
+ const uint8_t* data_;
+ const size_t length_;
+
+ ValueDeserializer deserializer_;
+};
+
+SerializerContext::SerializerContext(Environment* env, Local<Object> wrap)
+ : BaseObject(env, wrap),
+ serializer_(env->isolate(), this) {
+ MakeWeak<SerializerContext>(this);
+}
+
+void SerializerContext::ThrowDataCloneError(Local<String> message) {
+ Local<Value> args[1] = { message };
+ Local<Value> get_data_clone_error =
+ object()->Get(env()->context(),
+ env()->get_data_clone_error_string())
+ .ToLocalChecked();
+
+ CHECK(get_data_clone_error->IsFunction());
+ MaybeLocal<Value> error =
+ get_data_clone_error.As<Function>()->Call(env()->context(),
+ object(),
+ arraysize(args),
+ args);
+
+ if (error.IsEmpty()) return;
+
+ env()->isolate()->ThrowException(error.ToLocalChecked());
+}
+
+Maybe<uint32_t> SerializerContext::GetSharedArrayBufferId(
+ Isolate* isolate, Local<SharedArrayBuffer> shared_array_buffer) {
+ Local<Value> args[1] = { shared_array_buffer };
+ Local<Value> get_shared_array_buffer_id =
+ object()->Get(env()->context(),
+ env()->get_shared_array_buffer_id_string())
+ .ToLocalChecked();
+
+ if (!get_shared_array_buffer_id->IsFunction()) {
+ return ValueSerializer::Delegate::GetSharedArrayBufferId(
+ isolate, shared_array_buffer);
+ }
+
+ MaybeLocal<Value> id =
+ get_shared_array_buffer_id.As<Function>()->Call(env()->context(),
+ object(),
+ arraysize(args),
+ args);
+
+ if (id.IsEmpty()) return Nothing<uint32_t>();
+
+ return id.ToLocalChecked()->Uint32Value(env()->context());
+}
+
+Maybe<bool> SerializerContext::WriteHostObject(Isolate* isolate,
+ Local<Object> input) {
+ MaybeLocal<Value> ret;
+ Local<Value> args[1] = { input };
+
+ Local<Value> write_host_object =
+ object()->Get(env()->context(),
+ env()->write_host_object_string()).ToLocalChecked();
+
+ if (!write_host_object->IsFunction()) {
+ return ValueSerializer::Delegate::WriteHostObject(isolate, input);
+ }
+
+ ret = write_host_object.As<Function>()->Call(env()->context(),
+ object(),
+ arraysize(args),
+ args);
+
+ if (ret.IsEmpty())
+ return Nothing<bool>();
+
+ return Just(true);
+}
+
+void SerializerContext::New(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+
+ new SerializerContext(env, args.This());
+}
+
+void SerializerContext::WriteHeader(const FunctionCallbackInfo<Value>& args) {
+ SerializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+ ctx->serializer_.WriteHeader();
+}
+
+void SerializerContext::WriteValue(const FunctionCallbackInfo<Value>& args) {
+ SerializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+ Maybe<bool> ret =
+ ctx->serializer_.WriteValue(ctx->env()->context(), args[0]);
+
+ if (ret.IsJust()) args.GetReturnValue().Set(ret.FromJust());
+}
+
+void SerializerContext::SetTreatArrayBufferViewsAsHostObjects(
+ const FunctionCallbackInfo<Value>& args) {
+ SerializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+
+ Maybe<bool> value = args[0]->BooleanValue(ctx->env()->context());
+ if (value.IsNothing()) return;
+ ctx->serializer_.SetTreatArrayBufferViewsAsHostObjects(value.FromJust());
+}
+
+void SerializerContext::ReleaseBuffer(const FunctionCallbackInfo<Value>& args) {
+ SerializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+
+ std::pair<uint8_t*, size_t> ret = ctx->serializer_.Release();
+ auto buf = Buffer::New(ctx->env(),
+ reinterpret_cast<char*>(ret.first),
+ ret.second);
+
+ if (!buf.IsEmpty()) {
+ args.GetReturnValue().Set(buf.ToLocalChecked());
+ }
+}
+
+void SerializerContext::TransferArrayBuffer(
+ const FunctionCallbackInfo<Value>& args) {
+ SerializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+
+ Maybe<uint32_t> id = args[0]->Uint32Value(ctx->env()->context());
+ if (id.IsNothing()) return;
+
+ if (!args[1]->IsArrayBuffer())
+ return ctx->env()->ThrowTypeError("arrayBuffer must be an ArrayBuffer");
+
+ Local<ArrayBuffer> ab = args[1].As<ArrayBuffer>();
+ ctx->serializer_.TransferArrayBuffer(id.FromJust(), ab);
+ return;
+}
+
+void SerializerContext::WriteUint32(const FunctionCallbackInfo<Value>& args) {
+ SerializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+
+ Maybe<uint32_t> value = args[0]->Uint32Value(ctx->env()->context());
+ if (value.IsNothing()) return;
+
+ ctx->serializer_.WriteUint32(value.FromJust());
+}
+
+void SerializerContext::WriteUint64(const FunctionCallbackInfo<Value>& args) {
+ SerializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+
+ Maybe<uint32_t> arg0 = args[0]->Uint32Value(ctx->env()->context());
+ Maybe<uint32_t> arg1 = args[1]->Uint32Value(ctx->env()->context());
+ if (arg0.IsNothing() || arg1.IsNothing())
+ return;
+
+ uint64_t hi = arg0.FromJust();
+ uint64_t lo = arg1.FromJust();
+ ctx->serializer_.WriteUint64((hi << 32) | lo);
+}
+
+void SerializerContext::WriteDouble(const FunctionCallbackInfo<Value>& args) {
+ SerializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+
+ Maybe<double> value = args[0]->NumberValue(ctx->env()->context());
+ if (value.IsNothing()) return;
+
+ ctx->serializer_.WriteDouble(value.FromJust());
+}
+
+void SerializerContext::WriteRawBytes(const FunctionCallbackInfo<Value>& args) {
+ SerializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+
+ if (!args[0]->IsUint8Array()) {
+ return ctx->env()->ThrowTypeError("source must be a Uint8Array");
+ }
+
+ ctx->serializer_.WriteRawBytes(Buffer::Data(args[0]),
+ Buffer::Length(args[0]));
+}
+
+DeserializerContext::DeserializerContext(Environment* env,
+ Local<Object> wrap,
+ Local<Value> buffer)
+ : BaseObject(env, wrap),
+ data_(reinterpret_cast<const uint8_t*>(Buffer::Data(buffer))),
+ length_(Buffer::Length(buffer)),
+ deserializer_(env->isolate(), data_, length_, this) {
+ object()->Set(env->context(), env->buffer_string(), buffer);
+
+ MakeWeak<DeserializerContext>(this);
+}
+
+MaybeLocal<Object> DeserializerContext::ReadHostObject(Isolate* isolate) {
+ Local<Value> read_host_object =
+ object()->Get(env()->context(),
+ env()->read_host_object_string()).ToLocalChecked();
+
+ if (!read_host_object->IsFunction()) {
+ return ValueDeserializer::Delegate::ReadHostObject(isolate);
+ }
+
+ MaybeLocal<Value> ret =
+ read_host_object.As<Function>()->Call(env()->context(),
+ object(),
+ 0,
+ nullptr);
+
+ if (ret.IsEmpty())
+ return MaybeLocal<Object>();
+
+ Local<Value> return_value = ret.ToLocalChecked();
+ if (!return_value->IsObject()) {
+ env()->ThrowTypeError("readHostObject must return an object");
+ return MaybeLocal<Object>();
+ }
+
+ return return_value.As<Object>();
+}
+
+void DeserializerContext::New(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+
+ if (!args[0]->IsUint8Array()) {
+ return env->ThrowTypeError("buffer must be a Uint8Array");
+ }
+
+ new DeserializerContext(env, args.This(), args[0]);
+}
+
+void DeserializerContext::ReadHeader(const FunctionCallbackInfo<Value>& args) {
+ DeserializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+
+ Maybe<bool> ret = ctx->deserializer_.ReadHeader(ctx->env()->context());
+
+ if (ret.IsJust()) args.GetReturnValue().Set(ret.FromJust());
+}
+
+void DeserializerContext::ReadValue(const FunctionCallbackInfo<Value>& args) {
+ DeserializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+
+ MaybeLocal<Value> ret = ctx->deserializer_.ReadValue(ctx->env()->context());
+
+ if (!ret.IsEmpty()) args.GetReturnValue().Set(ret.ToLocalChecked());
+}
+
+void DeserializerContext::TransferArrayBuffer(
+ const FunctionCallbackInfo<Value>& args) {
+ DeserializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+
+ Maybe<uint32_t> id = args[0]->Uint32Value(ctx->env()->context());
+ if (id.IsNothing()) return;
+
+ if (args[1]->IsArrayBuffer()) {
+ Local<ArrayBuffer> ab = args[1].As<ArrayBuffer>();
+ ctx->deserializer_.TransferArrayBuffer(id.FromJust(), ab);
+ return;
+ }
+
+ if (args[1]->IsSharedArrayBuffer()) {
+ Local<SharedArrayBuffer> sab = args[1].As<SharedArrayBuffer>();
+ ctx->deserializer_.TransferSharedArrayBuffer(id.FromJust(), sab);
+ return;
+ }
+
+ return ctx->env()->ThrowTypeError("arrayBuffer must be an ArrayBuffer or "
+ "SharedArrayBuffer");
+}
+
+void DeserializerContext::GetWireFormatVersion(
+ const FunctionCallbackInfo<Value>& args) {
+ DeserializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+
+ args.GetReturnValue().Set(ctx->deserializer_.GetWireFormatVersion());
+}
+
+void DeserializerContext::ReadUint32(const FunctionCallbackInfo<Value>& args) {
+ DeserializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+
+ uint32_t value;
+ bool ok = ctx->deserializer_.ReadUint32(&value);
+ if (!ok) return ctx->env()->ThrowError("ReadUint32() failed");
+ return args.GetReturnValue().Set(value);
+}
+
+void DeserializerContext::ReadUint64(const FunctionCallbackInfo<Value>& args) {
+ DeserializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+
+ uint64_t value;
+ bool ok = ctx->deserializer_.ReadUint64(&value);
+ if (!ok) return ctx->env()->ThrowError("ReadUint64() failed");
+
+ uint32_t hi = static_cast<uint32_t>(value >> 32);
+ uint32_t lo = static_cast<uint32_t>(value);
+
+ Isolate* isolate = ctx->env()->isolate();
+ Local<Context> context = ctx->env()->context();
+
+ Local<Array> ret = Array::New(isolate, 2);
+ ret->Set(context, 0, Integer::NewFromUnsigned(isolate, hi));
+ ret->Set(context, 1, Integer::NewFromUnsigned(isolate, lo));
+ return args.GetReturnValue().Set(ret);
+}
+
+void DeserializerContext::ReadDouble(const FunctionCallbackInfo<Value>& args) {
+ DeserializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+
+ double value;
+ bool ok = ctx->deserializer_.ReadDouble(&value);
+ if (!ok) return ctx->env()->ThrowError("ReadDouble() failed");
+ return args.GetReturnValue().Set(value);
+}
+
+void DeserializerContext::ReadRawBytes(
+ const FunctionCallbackInfo<Value>& args) {
+ DeserializerContext* ctx;
+ ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder());
+
+ Maybe<int64_t> length_arg = args[0]->IntegerValue(ctx->env()->context());
+ if (length_arg.IsNothing()) return;
+ size_t length = length_arg.FromJust();
+
+ const void* data;
+ bool ok = ctx->deserializer_.ReadRawBytes(length, &data);
+ if (!ok) return ctx->env()->ThrowError("ReadRawBytes() failed");
+
+ const uint8_t* position = reinterpret_cast<const uint8_t*>(data);
+ CHECK_GE(position, ctx->data_);
+ CHECK_LE(position + length, ctx->data_ + ctx->length_);
+
+ const uint32_t offset = position - ctx->data_;
+ CHECK_EQ(ctx->data_ + offset, position);
+
+ args.GetReturnValue().Set(offset);
+}
+
+void InitializeSerdesBindings(Local<Object> target,
+ Local<Value> unused,
+ Local<Context> context) {
+ Environment* env = Environment::GetCurrent(context);
+ Local<FunctionTemplate> ser =
+ env->NewFunctionTemplate(SerializerContext::New);
+
+ ser->InstanceTemplate()->SetInternalFieldCount(1);
+
+ env->SetProtoMethod(ser, "writeHeader", SerializerContext::WriteHeader);
+ env->SetProtoMethod(ser, "writeValue", SerializerContext::WriteValue);
+ env->SetProtoMethod(ser, "releaseBuffer", SerializerContext::ReleaseBuffer);
+ env->SetProtoMethod(ser,
+ "transferArrayBuffer",
+ SerializerContext::TransferArrayBuffer);
+ env->SetProtoMethod(ser, "writeUint32", SerializerContext::WriteUint32);
+ env->SetProtoMethod(ser, "writeUint64", SerializerContext::WriteUint64);
+ env->SetProtoMethod(ser, "writeDouble", SerializerContext::WriteDouble);
+ env->SetProtoMethod(ser, "writeRawBytes", SerializerContext::WriteRawBytes);
+ env->SetProtoMethod(ser,
+ "_setTreatArrayBufferViewsAsHostObjects",
+ SerializerContext::SetTreatArrayBufferViewsAsHostObjects);
+
+ ser->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Serializer"));
+ target->Set(env->context(),
+ FIXED_ONE_BYTE_STRING(env->isolate(), "Serializer"),
+ ser->GetFunction(env->context()).ToLocalChecked()).FromJust();
+
+ Local<FunctionTemplate> des =
+ env->NewFunctionTemplate(DeserializerContext::New);
+
+ des->InstanceTemplate()->SetInternalFieldCount(1);
+
+ env->SetProtoMethod(des, "readHeader", DeserializerContext::ReadHeader);
+ env->SetProtoMethod(des, "readValue", DeserializerContext::ReadValue);
+ env->SetProtoMethod(des,
+ "getWireFormatVersion",
+ DeserializerContext::GetWireFormatVersion);
+ env->SetProtoMethod(des,
+ "transferArrayBuffer",
+ DeserializerContext::TransferArrayBuffer);
+ env->SetProtoMethod(des, "readUint32", DeserializerContext::ReadUint32);
+ env->SetProtoMethod(des, "readUint64", DeserializerContext::ReadUint64);
+ env->SetProtoMethod(des, "readDouble", DeserializerContext::ReadDouble);
+ env->SetProtoMethod(des, "_readRawBytes", DeserializerContext::ReadRawBytes);
+
+ des->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Deserializer"));
+ target->Set(env->context(),
+ FIXED_ONE_BYTE_STRING(env->isolate(), "Deserializer"),
+ des->GetFunction(env->context()).ToLocalChecked()).FromJust();
+}
+
+} // namespace node
+
+NODE_MODULE_CONTEXT_AWARE_BUILTIN(serdes, node::InitializeSerdesBindings)
diff --git a/test/parallel/test-v8-serdes-sharedarraybuffer.js b/test/parallel/test-v8-serdes-sharedarraybuffer.js
new file mode 100644
index 0000000000..9dea5fe8b1
--- /dev/null
+++ b/test/parallel/test-v8-serdes-sharedarraybuffer.js
@@ -0,0 +1,27 @@
+/*global SharedArrayBuffer*/
+'use strict';
+// Flags: --harmony-sharedarraybuffer
+
+const common = require('../common');
+const assert = require('assert');
+const v8 = require('v8');
+
+{
+ const sab = new SharedArrayBuffer(64);
+ const uint8array = new Uint8Array(sab);
+ const ID = 42;
+
+ const ser = new v8.Serializer();
+ ser._getSharedArrayBufferId = common.mustCall(() => ID);
+ ser.writeHeader();
+
+ ser.writeValue(uint8array);
+
+ const des = new v8.Deserializer(ser.releaseBuffer());
+ des.readHeader();
+ des.transferArrayBuffer(ID, sab);
+
+ const value = des.readValue();
+ assert.strictEqual(value.buffer, sab);
+ assert.notStrictEqual(value, uint8array);
+}
diff --git a/test/parallel/test-v8-serdes.js b/test/parallel/test-v8-serdes.js
new file mode 100644
index 0000000000..84037b6f8c
--- /dev/null
+++ b/test/parallel/test-v8-serdes.js
@@ -0,0 +1,120 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const v8 = require('v8');
+
+const circular = {};
+circular.circular = circular;
+
+const objects = [
+ { foo: 'bar' },
+ { bar: 'baz' },
+ new Uint8Array([1, 2, 3, 4]),
+ new Uint32Array([1, 2, 3, 4]),
+ Buffer.from([1, 2, 3, 4]),
+ undefined,
+ null,
+ 42,
+ circular
+];
+
+{
+ const ser = new v8.DefaultSerializer();
+ ser.writeHeader();
+ for (const obj of objects) {
+ ser.writeValue(obj);
+ }
+
+ const des = new v8.DefaultDeserializer(ser.releaseBuffer());
+ des.readHeader();
+
+ for (const obj of objects) {
+ assert.deepStrictEqual(des.readValue(), obj);
+ }
+}
+
+{
+ for (const obj of objects) {
+ assert.deepStrictEqual(v8.deserialize(v8.serialize(obj)), obj);
+ }
+}
+
+{
+ const ser = new v8.DefaultSerializer();
+ ser._getDataCloneError = common.mustCall((message) => {
+ assert.strictEqual(message, '[object Object] could not be cloned.');
+ return new Error('foobar');
+ });
+
+ ser.writeHeader();
+
+ assert.throws(() => {
+ ser.writeValue(new Proxy({}, {}));
+ }, /foobar/);
+}
+
+{
+ const ser = new v8.DefaultSerializer();
+ ser._writeHostObject = common.mustCall((object) => {
+ assert.strictEqual(object, process.stdin._handle);
+ const buf = Buffer.from('stdin');
+
+ ser.writeUint32(buf.length);
+ ser.writeRawBytes(buf);
+
+ ser.writeUint64(1, 2);
+ ser.writeDouble(-0.25);
+ });
+
+ ser.writeHeader();
+ ser.writeValue({ val: process.stdin._handle });
+
+ const des = new v8.DefaultDeserializer(ser.releaseBuffer());
+ des._readHostObject = common.mustCall(() => {
+ const length = des.readUint32();
+ const buf = des.readRawBytes(length);
+
+ assert.strictEqual(buf.toString(), 'stdin');
+
+ assert.deepStrictEqual(des.readUint64(), [1, 2]);
+ assert.strictEqual(des.readDouble(), -0.25);
+ return process.stdin._handle;
+ });
+
+ des.readHeader();
+
+ assert.strictEqual(des.readValue().val, process.stdin._handle);
+}
+
+{
+ const ser = new v8.DefaultSerializer();
+ ser._writeHostObject = common.mustCall((object) => {
+ throw new Error('foobar');
+ });
+
+ ser.writeHeader();
+ assert.throws(() => {
+ ser.writeValue({ val: process.stdin._handle });
+ }, /foobar/);
+}
+
+{
+ assert.throws(() => v8.serialize(process.stdin._handle),
+ /^Error: Unknown host object type: \[object .*\]$/);
+}
+
+{
+ const buf = Buffer.from('ff0d6f2203666f6f5e007b01', 'hex');
+
+ const des = new v8.DefaultDeserializer(buf);
+ des.readHeader();
+
+ const ser = new v8.DefaultSerializer();
+ ser.writeHeader();
+
+ ser.writeValue(des.readValue());
+
+ assert.deepStrictEqual(buf, ser.releaseBuffer());
+ assert.strictEqual(des.getWireFormatVersion(), 0x0d);
+}