v8: expose new V8 serialization API
Expose the new serialization API that was added in V8 5.5 to userland. The JS API is virtually a direct copy of what V8 provides on the C++ level. This is useful Node as a possible replacement for some internals that currently use JSON, like IPC, but is likely to be useful to general userland code as well. PR-URL: https://github.com/nodejs/node/pull/11048 Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
This commit is contained in:
parent
6d93508369
commit
1fde98bb4f
251
doc/api/v8.md
251
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
|
||||
|
119
lib/v8.js
119
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();
|
||||
};
|
||||
|
1
node.gyp
1
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',
|
||||
|
@ -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") \
|
||||
|
483
src/node_serdes.cc
Normal file
483
src/node_serdes.cc
Normal file
@ -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)
|
27
test/parallel/test-v8-serdes-sharedarraybuffer.js
Normal file
27
test/parallel/test-v8-serdes-sharedarraybuffer.js
Normal file
@ -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);
|
||||
}
|
120
test/parallel/test-v8-serdes.js
Normal file
120
test/parallel/test-v8-serdes.js
Normal file
@ -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);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user