• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Flags: --expose-internals
2
3'use strict';
4
5const common = require('../common');
6const { internalBinding } = require('internal/test/binding');
7const assert = require('assert');
8const v8 = require('v8');
9const os = require('os');
10
11const circular = {};
12circular.circular = circular;
13
14const objects = [
15  { foo: 'bar' },
16  { bar: 'baz' },
17  new Int8Array([1, 2, 3, 4]),
18  new Uint8Array([1, 2, 3, 4]),
19  new Int16Array([1, 2, 3, 4]),
20  new Uint16Array([1, 2, 3, 4]),
21  new Int32Array([1, 2, 3, 4]),
22  new Uint32Array([1, 2, 3, 4]),
23  new Float32Array([1, 2, 3, 4]),
24  new Float64Array([1, 2, 3, 4]),
25  new DataView(new ArrayBuffer(42)),
26  Buffer.from([1, 2, 3, 4]),
27  new BigInt64Array([42n]),
28  new BigUint64Array([42n]),
29  undefined,
30  null,
31  42,
32  circular,
33];
34
35const hostObject = new (internalBinding('js_stream').JSStream)();
36
37{
38  const ser = new v8.DefaultSerializer();
39  ser.writeHeader();
40  for (const obj of objects) {
41    ser.writeValue(obj);
42  }
43
44  const des = new v8.DefaultDeserializer(ser.releaseBuffer());
45  des.readHeader();
46
47  for (const obj of objects) {
48    assert.deepStrictEqual(des.readValue(), obj);
49  }
50}
51
52{
53  for (const obj of objects) {
54    assert.deepStrictEqual(v8.deserialize(v8.serialize(obj)), obj);
55  }
56}
57
58{
59  const ser = new v8.DefaultSerializer();
60  ser._getDataCloneError = common.mustCall((message) => {
61    assert.strictEqual(message, '#<Object> could not be cloned.');
62    return new Error('foobar');
63  });
64
65  ser.writeHeader();
66
67  assert.throws(() => {
68    ser.writeValue(new Proxy({}, {}));
69  }, /foobar/);
70}
71
72{
73  const ser = new v8.DefaultSerializer();
74  ser._writeHostObject = common.mustCall((object) => {
75    assert.strictEqual(object, hostObject);
76    const buf = Buffer.from('hostObjectTag');
77
78    ser.writeUint32(buf.length);
79    ser.writeRawBytes(buf);
80
81    ser.writeUint64(1, 2);
82    ser.writeDouble(-0.25);
83  });
84
85  ser.writeHeader();
86  ser.writeValue({ val: hostObject });
87
88  const des = new v8.DefaultDeserializer(ser.releaseBuffer());
89  des._readHostObject = common.mustCall(() => {
90    const length = des.readUint32();
91    const buf = des.readRawBytes(length);
92
93    assert.strictEqual(buf.toString(), 'hostObjectTag');
94
95    assert.deepStrictEqual(des.readUint64(), [1, 2]);
96    assert.strictEqual(des.readDouble(), -0.25);
97    return hostObject;
98  });
99
100  des.readHeader();
101
102  assert.strictEqual(des.readValue().val, hostObject);
103}
104
105// This test ensures that `v8.Serializer.writeRawBytes()` support
106// `TypedArray` and `DataView`.
107{
108  const text = 'hostObjectTag';
109  const data = Buffer.from(text);
110  const arrayBufferViews = common.getArrayBufferViews(data);
111
112  // `buf` is one of `TypedArray` or `DataView`.
113  function testWriteRawBytes(buf) {
114    let writeHostObjectCalled = false;
115    const ser = new v8.DefaultSerializer();
116
117    ser._writeHostObject = common.mustCall((object) => {
118      writeHostObjectCalled = true;
119      ser.writeUint32(buf.byteLength);
120      ser.writeRawBytes(buf);
121    });
122
123    ser.writeHeader();
124    ser.writeValue({ val: hostObject });
125
126    const des = new v8.DefaultDeserializer(ser.releaseBuffer());
127    des._readHostObject = common.mustCall(() => {
128      assert.strictEqual(writeHostObjectCalled, true);
129      const length = des.readUint32();
130      const buf = des.readRawBytes(length);
131      assert.strictEqual(buf.toString(), text);
132
133      return hostObject;
134    });
135
136    des.readHeader();
137
138    assert.strictEqual(des.readValue().val, hostObject);
139  }
140
141  arrayBufferViews.forEach((buf) => {
142    testWriteRawBytes(buf);
143  });
144}
145
146{
147  const ser = new v8.DefaultSerializer();
148  ser._writeHostObject = common.mustCall((object) => {
149    throw new Error('foobar');
150  });
151
152  ser.writeHeader();
153  assert.throws(() => {
154    ser.writeValue({ val: hostObject });
155  }, /foobar/);
156}
157
158{
159  assert.throws(() => v8.serialize(hostObject), {
160    constructor: Error,
161    message: 'Unserializable host object: JSStream {}'
162  });
163}
164
165{
166  // Test that an old serialized value can still be deserialized.
167  const buf = Buffer.from('ff0d6f2203666f6f5e007b01', 'hex');
168
169  const des = new v8.DefaultDeserializer(buf);
170  des.readHeader();
171  assert.strictEqual(des.getWireFormatVersion(), 0x0d);
172
173  const value = des.readValue();
174  assert.strictEqual(value, value.foo);
175}
176
177{
178  const message = `New serialization format.
179
180    This test is expected to fail when V8 changes its serialization format.
181    When that happens, the "desStr" variable must be updated to the new value
182    and the change should be mentioned in the release notes, as it is semver-major.
183
184    Consider opening an issue as a heads up at https://github.com/nodejs/node/issues/new
185  `;
186
187  const desStr = 'ff0f6f2203666f6f5e007b01';
188
189  const desBuf = Buffer.from(desStr, 'hex');
190  const des = new v8.DefaultDeserializer(desBuf);
191  des.readHeader();
192  const value = des.readValue();
193
194  const ser = new v8.DefaultSerializer();
195  ser.writeHeader();
196  ser.writeValue(value);
197
198  const serBuf = ser.releaseBuffer();
199  const serStr = serBuf.toString('hex');
200  assert.deepStrictEqual(serStr, desStr, message);
201}
202
203{
204  // Unaligned Uint16Array read, with padding in the underlying array buffer.
205  let buf = Buffer.alloc(32 + 9);
206  buf.write('ff0d5c0404addeefbe', 32, 'hex');
207  buf = buf.slice(32);
208
209  const expectedResult = os.endianness() === 'LE' ?
210    new Uint16Array([0xdead, 0xbeef]) : new Uint16Array([0xadde, 0xefbe]);
211
212  assert.deepStrictEqual(v8.deserialize(buf), expectedResult);
213}
214
215{
216  assert.throws(() => v8.Serializer(), {
217    constructor: TypeError,
218    message: "Class constructor Serializer cannot be invoked without 'new'",
219    code: 'ERR_CONSTRUCT_CALL_REQUIRED'
220  });
221  assert.throws(() => v8.Deserializer(), {
222    constructor: TypeError,
223    message: "Class constructor Deserializer cannot be invoked without 'new'",
224    code: 'ERR_CONSTRUCT_CALL_REQUIRED'
225  });
226}
227
228
229// `v8.deserialize()` and `new v8.Deserializer()` should support both
230// `TypedArray` and `DataView`.
231{
232  for (const obj of objects) {
233    const buf = v8.serialize(obj);
234
235    for (const arrayBufferView of common.getArrayBufferViews(buf)) {
236      assert.deepStrictEqual(v8.deserialize(arrayBufferView), obj);
237    }
238
239    for (const arrayBufferView of common.getArrayBufferViews(buf)) {
240      const deserializer = new v8.DefaultDeserializer(arrayBufferView);
241      deserializer.readHeader();
242      const value = deserializer.readValue();
243      assert.deepStrictEqual(value, obj);
244
245      const serializer = new v8.DefaultSerializer();
246      serializer.writeHeader();
247      serializer.writeValue(value);
248      assert.deepStrictEqual(buf, serializer.releaseBuffer());
249    }
250  }
251}
252
253{
254  const INVALID_SOURCE = 'INVALID_SOURCE_TYPE';
255  const serializer = new v8.Serializer();
256  serializer.writeHeader();
257  assert.throws(
258    () => serializer.writeRawBytes(INVALID_SOURCE),
259    /^TypeError: source must be a TypedArray or a DataView$/,
260  );
261  assert.throws(
262    () => v8.deserialize(INVALID_SOURCE),
263    /^TypeError: buffer must be a TypedArray or a DataView$/,
264  );
265  assert.throws(
266    () => new v8.Deserializer(INVALID_SOURCE),
267    /^TypeError: buffer must be a TypedArray or a DataView$/,
268  );
269}
270
271{
272  // Regression test for https://github.com/nodejs/node/issues/37978
273  assert.throws(() => {
274    new v8.Deserializer(new v8.Serializer().releaseBuffer()).readDouble();
275  }, /ReadDouble\(\) failed/);
276}
277