Skip to content

Commit 1fde98b

Browse files
committed
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: #11048 Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]>
1 parent 6d93508 commit 1fde98b

File tree

7 files changed

+1005
-0
lines changed

7 files changed

+1005
-0
lines changed

doc/api/v8.md

+251
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,254 @@ setTimeout(function() { v8.setFlagsFromString('--notrace_gc'); }, 60e3);
157157
[`vm.Script`]: vm.html#vm_new_vm_script_code_options
158158
[here]: https://github.com/thlorenz/v8-flags/blob/master/flags-0.11.md
159159
[`GetHeapSpaceStatistics`]: https://v8docs.nodesource.com/node-5.0/d5/dda/classv8_1_1_isolate.html#ac673576f24fdc7a33378f8f57e1d13a4
160+
161+
## Serialization API
162+
163+
> Stability: 1 - Experimental
164+
165+
The serialization API provides means of serializing JavaScript values in a way
166+
that is compatible with the [HTML structured clone algorithm][].
167+
The format is backward-compatible (i.e. safe to store to disk).
168+
169+
*Note*: This API is under development, and changes (including incompatible
170+
changes to the API or wire format) may occur until this warning is removed.
171+
172+
### v8.serialize(value)
173+
<!--
174+
added: REPLACEME
175+
-->
176+
177+
* Returns: {Buffer}
178+
179+
Uses a [`DefaultSerializer`][] to serialize `value` into a buffer.
180+
181+
### v8.deserialize(buffer)
182+
<!--
183+
added: REPLACEME
184+
-->
185+
186+
* `buffer` {Buffer|Uint8Array} A buffer returned by [`serialize()`][].
187+
188+
Uses a [`DefaultDeserializer`][] with default options to read a JS value
189+
from a buffer.
190+
191+
### class: v8.Serializer
192+
<!--
193+
added: REPLACEME
194+
-->
195+
196+
#### new Serializer()
197+
Creates a new `Serializer` object.
198+
199+
#### serializer.writeHeader()
200+
201+
Writes out a header, which includes the serialization format version.
202+
203+
#### serializer.writeValue(value)
204+
205+
Serializes a JavaScript value and adds the serialized representation to the
206+
internal buffer.
207+
208+
This throws an error if `value` cannot be serialized.
209+
210+
#### serializer.releaseBuffer()
211+
212+
Returns the stored internal buffer. This serializer should not be used once
213+
the buffer is released. Calling this method results in undefined behavior
214+
if a previous write has failed.
215+
216+
#### serializer.transferArrayBuffer(id, arrayBuffer)
217+
218+
* `id` {integer} A 32-bit unsigned integer.
219+
* `arrayBuffer` {ArrayBuffer} An `ArrayBuffer` instance.
220+
221+
Marks an `ArrayBuffer` as havings its contents transferred out of band.
222+
Pass the corresponding `ArrayBuffer` in the deserializing context to
223+
[`deserializer.transferArrayBuffer()`][].
224+
225+
#### serializer.writeUint32(value)
226+
227+
* `value` {integer}
228+
229+
Write a raw 32-bit unsigned integer.
230+
For use inside of a custom [`serializer._writeHostObject()`][].
231+
232+
#### serializer.writeUint64(hi, lo)
233+
234+
* `hi` {integer}
235+
* `lo` {integer}
236+
237+
Write a raw 64-bit unsigned integer, split into high and low 32-bit parts.
238+
For use inside of a custom [`serializer._writeHostObject()`][].
239+
240+
#### serializer.writeDouble(value)
241+
242+
* `value` {number}
243+
244+
Write a JS `number` value.
245+
For use inside of a custom [`serializer._writeHostObject()`][].
246+
247+
#### serializer.writeRawBytes(buffer)
248+
249+
* `buffer` {Buffer|Uint8Array}
250+
251+
Write raw bytes into the serializer’s internal buffer. The deserializer
252+
will require a way to compute the length of the buffer.
253+
For use inside of a custom [`serializer._writeHostObject()`][].
254+
255+
#### serializer.\_writeHostObject(object)
256+
257+
* `object` {Object}
258+
259+
This method is called to write some kind of host object, i.e. an object created
260+
by native C++ bindings. If it is not possible to serialize `object`, a suitable
261+
exception should be thrown.
262+
263+
This method is not present on the `Serializer` class itself but can be provided
264+
by subclasses.
265+
266+
#### serializer.\_getDataCloneError(message)
267+
268+
* `message` {string}
269+
270+
This method is called to generate error objects that will be thrown when an
271+
object can not be cloned.
272+
273+
This method defaults to the [`Error`][] constructor and can be be overridden on
274+
subclasses.
275+
276+
#### serializer.\_getSharedArrayBufferId(sharedArrayBuffer)
277+
278+
* `sharedArrayBuffer` {SharedArrayBuffer}
279+
280+
This method is called when the serializer is going to serialize a
281+
`SharedArrayBuffer` object. It must return an unsigned 32-bit integer ID for
282+
the object, using the same ID if this `SharedArrayBuffer` has already been
283+
serialized. When deserializing, this ID will be passed to
284+
[`deserializer.transferArrayBuffer()`][].
285+
286+
If the object cannot be serialized, an exception should be thrown.
287+
288+
This method is not present on the `Serializer` class itself but can be provided
289+
by subclasses.
290+
291+
#### serializer.\_setTreatArrayBufferViewsAsHostObjects(flag)
292+
293+
* `flag` {boolean}
294+
295+
Indicate whether to treat `TypedArray` and `DataView` objects as
296+
host objects, i.e. pass them to [`serializer._writeHostObject`][].
297+
298+
The default is not to treat those objects as host objects.
299+
300+
### class: v8.Deserializer
301+
<!--
302+
added: REPLACEME
303+
-->
304+
305+
#### new Deserializer(buffer)
306+
307+
* `buffer` {Buffer|Uint8Array} A buffer returned by [`serializer.releaseBuffer()`][].
308+
309+
Creates a new `Deserializer` object.
310+
311+
#### deserializer.readHeader()
312+
313+
Reads and validates a header (including the format version).
314+
May, for example, reject an invalid or unsupported wire format. In that case,
315+
an `Error` is thrown.
316+
317+
#### deserializer.readValue()
318+
319+
Deserializes a JavaScript value from the buffer and returns it.
320+
321+
#### deserializer.transferArrayBuffer(id, arrayBuffer)
322+
323+
* `id` {integer} A 32-bit unsigned integer.
324+
* `arrayBuffer` {ArrayBuffer|SharedArrayBuffer} An `ArrayBuffer` instance.
325+
326+
Marks an `ArrayBuffer` as havings its contents transferred out of band.
327+
Pass the corresponding `ArrayBuffer` in the serializing context to
328+
[`serializer.transferArrayBuffer()`][] (or return the `id` from
329+
[`serializer._getSharedArrayBufferId()`][] in the case of `SharedArrayBuffer`s).
330+
331+
#### deserializer.getWireFormatVersion()
332+
333+
* Returns: {integer}
334+
335+
Reads the underlying wire format version. Likely mostly to be useful to
336+
legacy code reading old wire format versions. May not be called before
337+
`.readHeader()`.
338+
339+
#### deserializer.readUint32()
340+
341+
* Returns: {integer}
342+
343+
Read a raw 32-bit unsigned integer and return it.
344+
For use inside of a custom [`deserializer._readHostObject()`][].
345+
346+
#### deserializer.readUint64()
347+
348+
* Returns: {Array}
349+
350+
Read a raw 64-bit unsigned integer and return it as an array `[hi, lo]`
351+
with two 32-bit unsigned integer entries.
352+
For use inside of a custom [`deserializer._readHostObject()`][].
353+
354+
#### deserializer.readDouble()
355+
356+
* Returns: {number}
357+
358+
Read a JS `number` value.
359+
For use inside of a custom [`deserializer._readHostObject()`][].
360+
361+
#### deserializer.readRawBytes(length)
362+
363+
* Returns: {Buffer}
364+
365+
Read raw bytes from the deserializer’s internal buffer. The `length` parameter
366+
must correspond to the length of the buffer that was passed to
367+
[`serializer.writeRawBytes()`][].
368+
For use inside of a custom [`deserializer._readHostObject()`][].
369+
370+
#### deserializer.\_readHostObject()
371+
372+
This method is called to read some kind of host object, i.e. an object that is
373+
created by native C++ bindings. If it is not possible to deserialize the data,
374+
a suitable exception should be thrown.
375+
376+
This method is not present on the `Deserializer` class itself but can be
377+
provided by subclasses.
378+
379+
### class: v8.DefaultSerializer
380+
<!--
381+
added: REPLACEME
382+
-->
383+
384+
A subclass of [`Serializer`][] that serializes `TypedArray`
385+
(in particular [`Buffer`][]) and `DataView` objects as host objects, and only
386+
stores the part of their underlying `ArrayBuffer`s that they are referring to.
387+
388+
### class: v8.DefaultDeserializer
389+
<!--
390+
added: REPLACEME
391+
-->
392+
393+
A subclass of [`Deserializer`][] corresponding to the format written by
394+
[`DefaultSerializer`][].
395+
396+
[`Buffer`]: buffer.html
397+
[`Error`]: errors.html#errors_class_error
398+
[`deserializer.transferArrayBuffer()`]: #v8_deserializer_transferarraybuffer_id_arraybuffer
399+
[`deserializer._readHostObject()`]: #v8_deserializer_readhostobject
400+
[`serializer.transferArrayBuffer()`]: #v8_serializer_transferarraybuffer_id_arraybuffer
401+
[`serializer.releaseBuffer()`]: #v8_serializer_releasebuffer
402+
[`serializer.writeRawBytes()`]: #v8_serializer_writerawbytes_buffer
403+
[`serializer._writeHostObject()`]: #v8_serializer_writehostobject_object
404+
[`serializer._getSharedArrayBufferId()`]: #v8_serializer_getsharedarraybufferid_sharedarraybuffer
405+
[`Serializer`]: #v8_class_v8_serializer
406+
[`Deserializer`]: #v8_class_v8_deserializer
407+
[`DefaultSerializer`]: #v8_class_v8_defaultserializer
408+
[`DefaultDeserializer`]: #v8_class_v8_defaultdeserializer
409+
[`serialize()`]: #v8_v8_serialize_value
410+
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm

lib/v8.js

+119
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@
1414

1515
'use strict';
1616

17+
const Buffer = require('buffer').Buffer;
18+
1719
const v8binding = process.binding('v8');
20+
const serdesBinding = process.binding('serdes');
21+
const bufferBinding = process.binding('buffer');
22+
23+
const { objectToString } = require('internal/util');
24+
const { FastBuffer } = require('internal/buffer');
1825

1926
// Properties for heap statistics buffer extraction.
2027
const heapStatisticsBuffer =
@@ -80,3 +87,115 @@ exports.getHeapSpaceStatistics = function() {
8087

8188
return heapSpaceStatistics;
8289
};
90+
91+
/* V8 serialization API */
92+
93+
const Serializer = exports.Serializer = serdesBinding.Serializer;
94+
const Deserializer = exports.Deserializer = serdesBinding.Deserializer;
95+
96+
/* JS methods for the base objects */
97+
Serializer.prototype._getDataCloneError = Error;
98+
99+
Deserializer.prototype.readRawBytes = function(length) {
100+
const offset = this._readRawBytes(length);
101+
// `this.buffer` can be a Buffer or a plain Uint8Array, so just calling
102+
// `.slice()` doesn't work.
103+
return new FastBuffer(this.buffer.buffer,
104+
this.buffer.byteOffset + offset,
105+
length);
106+
};
107+
108+
/* Keep track of how to handle different ArrayBufferViews.
109+
* The default Serializer for Node does not use the V8 methods for serializing
110+
* those objects because Node's `Buffer` objects use pooled allocation in many
111+
* cases, and their underlying `ArrayBuffer`s would show up in the
112+
* serialization. Because a) those may contain sensitive data and the user
113+
* may not be aware of that and b) they are often much larger than the `Buffer`
114+
* itself, custom serialization is applied. */
115+
const arrayBufferViewTypes = [Int8Array, Uint8Array, Uint8ClampedArray,
116+
Int16Array, Uint16Array, Int32Array, Uint32Array,
117+
Float32Array, Float64Array, DataView];
118+
119+
const arrayBufferViewTypeToIndex = new Map();
120+
121+
{
122+
const dummy = new ArrayBuffer();
123+
for (const [i, ctor] of arrayBufferViewTypes.entries()) {
124+
const tag = objectToString(new ctor(dummy));
125+
arrayBufferViewTypeToIndex.set(tag, i);
126+
}
127+
}
128+
129+
const bufferConstructorIndex = arrayBufferViewTypes.push(Buffer) - 1;
130+
131+
class DefaultSerializer extends Serializer {
132+
constructor() {
133+
super();
134+
135+
this._setTreatArrayBufferViewsAsHostObjects(true);
136+
}
137+
138+
_writeHostObject(abView) {
139+
let i = 0;
140+
if (abView.constructor === Buffer) {
141+
i = bufferConstructorIndex;
142+
} else {
143+
const tag = objectToString(abView);
144+
i = arrayBufferViewTypeToIndex.get(tag);
145+
146+
if (i === undefined) {
147+
throw this._getDataCloneError(`Unknown host object type: ${tag}`);
148+
}
149+
}
150+
this.writeUint32(i);
151+
this.writeUint32(abView.byteLength);
152+
this.writeRawBytes(new Uint8Array(abView.buffer,
153+
abView.byteOffset,
154+
abView.byteLength));
155+
}
156+
}
157+
158+
exports.DefaultSerializer = DefaultSerializer;
159+
160+
class DefaultDeserializer extends Deserializer {
161+
constructor(buffer) {
162+
super(buffer);
163+
}
164+
165+
_readHostObject() {
166+
const typeIndex = this.readUint32();
167+
const ctor = arrayBufferViewTypes[typeIndex];
168+
const byteLength = this.readUint32();
169+
const byteOffset = this._readRawBytes(byteLength);
170+
const BYTES_PER_ELEMENT = ctor.BYTES_PER_ELEMENT || 1;
171+
172+
const offset = this.buffer.byteOffset + byteOffset;
173+
if (offset % BYTES_PER_ELEMENT === 0) {
174+
return new ctor(this.buffer.buffer,
175+
offset,
176+
byteLength / BYTES_PER_ELEMENT);
177+
} else {
178+
// Copy to an aligned buffer first.
179+
const copy = Buffer.allocUnsafe(byteLength);
180+
bufferBinding.copy(this.buffer, copy, 0, offset, offset + byteLength);
181+
return new ctor(copy.buffer,
182+
copy.byteOffset,
183+
byteLength / BYTES_PER_ELEMENT);
184+
}
185+
}
186+
}
187+
188+
exports.DefaultDeserializer = DefaultDeserializer;
189+
190+
exports.serialize = function serialize(value) {
191+
const ser = new DefaultSerializer();
192+
ser.writeHeader();
193+
ser.writeValue(value);
194+
return ser.releaseBuffer();
195+
};
196+
197+
exports.deserialize = function deserialize(buffer) {
198+
const der = new DefaultDeserializer(buffer);
199+
der.readHeader();
200+
return der.readValue();
201+
};

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@
177177
'src/node_main.cc',
178178
'src/node_os.cc',
179179
'src/node_revert.cc',
180+
'src/node_serdes.cc',
180181
'src/node_url.cc',
181182
'src/node_util.cc',
182183
'src/node_v8.cc',

0 commit comments

Comments
 (0)