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