ObjectMiddleware.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. */
  4. "use strict";
  5. const createHash = require("../util/createHash");
  6. const ArraySerializer = require("./ArraySerializer");
  7. const DateObjectSerializer = require("./DateObjectSerializer");
  8. const ErrorObjectSerializer = require("./ErrorObjectSerializer");
  9. const MapObjectSerializer = require("./MapObjectSerializer");
  10. const NullPrototypeObjectSerializer = require("./NullPrototypeObjectSerializer");
  11. const PlainObjectSerializer = require("./PlainObjectSerializer");
  12. const RegExpObjectSerializer = require("./RegExpObjectSerializer");
  13. const SerializerMiddleware = require("./SerializerMiddleware");
  14. const SetObjectSerializer = require("./SetObjectSerializer");
  15. /** @typedef {typeof import("../util/Hash")} Hash */
  16. /** @typedef {import("./types").ComplexSerializableType} ComplexSerializableType */
  17. /** @typedef {import("./types").PrimitiveSerializableType} PrimitiveSerializableType */
  18. /** @typedef {new (...params: any[]) => any} Constructor */
  19. /*
  20. Format:
  21. File -> Section*
  22. Section -> ObjectSection | ReferenceSection | EscapeSection | OtherSection
  23. ObjectSection -> ESCAPE (
  24. number:relativeOffset (number > 0) |
  25. string:request (string|null):export
  26. ) Section:value* ESCAPE ESCAPE_END_OBJECT
  27. ReferenceSection -> ESCAPE number:relativeOffset (number < 0)
  28. EscapeSection -> ESCAPE ESCAPE_ESCAPE_VALUE (escaped value ESCAPE)
  29. EscapeSection -> ESCAPE ESCAPE_UNDEFINED (escaped value ESCAPE)
  30. OtherSection -> any (except ESCAPE)
  31. Why using null as escape value?
  32. Multiple null values can merged by the BinaryMiddleware, which makes it very efficient
  33. Technically any value can be used.
  34. */
  35. /**
  36. * @typedef {object} ObjectSerializerContext
  37. * @property {function(any): void} write
  38. * @property {(function(any): void)=} writeLazy
  39. * @property {(function(any, object=): (() => Promise<any> | any))=} writeSeparate
  40. * @property {function(any): void} setCircularReference
  41. */
  42. /**
  43. * @typedef {object} ObjectDeserializerContext
  44. * @property {function(): any} read
  45. * @property {function(any): void} setCircularReference
  46. */
  47. /**
  48. * @typedef {object} ObjectSerializer
  49. * @property {function(any, ObjectSerializerContext): void} serialize
  50. * @property {function(ObjectDeserializerContext): any} deserialize
  51. */
  52. /**
  53. * @template T
  54. * @param {Set<T>} set set
  55. * @param {number} size count of items to keep
  56. */
  57. const setSetSize = (set, size) => {
  58. let i = 0;
  59. for (const item of set) {
  60. if (i++ >= size) {
  61. set.delete(item);
  62. }
  63. }
  64. };
  65. /**
  66. * @template K, X
  67. * @param {Map<K, X>} map map
  68. * @param {number} size count of items to keep
  69. */
  70. const setMapSize = (map, size) => {
  71. let i = 0;
  72. for (const item of map.keys()) {
  73. if (i++ >= size) {
  74. map.delete(item);
  75. }
  76. }
  77. };
  78. /**
  79. * @param {Buffer} buffer buffer
  80. * @param {string | Hash} hashFunction hash function to use
  81. * @returns {string} hash
  82. */
  83. const toHash = (buffer, hashFunction) => {
  84. const hash = createHash(hashFunction);
  85. hash.update(buffer);
  86. return /** @type {string} */ (hash.digest("latin1"));
  87. };
  88. const ESCAPE = null;
  89. const ESCAPE_ESCAPE_VALUE = null;
  90. const ESCAPE_END_OBJECT = true;
  91. const ESCAPE_UNDEFINED = false;
  92. const CURRENT_VERSION = 2;
  93. /** @type {Map<Constructor, { request?: string, name?: string | number | null, serializer?: ObjectSerializer }>} */
  94. const serializers = new Map();
  95. /** @type {Map<string | number, ObjectSerializer>} */
  96. const serializerInversed = new Map();
  97. /** @type {Set<string>} */
  98. const loadedRequests = new Set();
  99. const NOT_SERIALIZABLE = {};
  100. const jsTypes = new Map();
  101. jsTypes.set(Object, new PlainObjectSerializer());
  102. jsTypes.set(Array, new ArraySerializer());
  103. jsTypes.set(null, new NullPrototypeObjectSerializer());
  104. jsTypes.set(Map, new MapObjectSerializer());
  105. jsTypes.set(Set, new SetObjectSerializer());
  106. jsTypes.set(Date, new DateObjectSerializer());
  107. jsTypes.set(RegExp, new RegExpObjectSerializer());
  108. jsTypes.set(Error, new ErrorObjectSerializer(Error));
  109. jsTypes.set(EvalError, new ErrorObjectSerializer(EvalError));
  110. jsTypes.set(RangeError, new ErrorObjectSerializer(RangeError));
  111. jsTypes.set(ReferenceError, new ErrorObjectSerializer(ReferenceError));
  112. jsTypes.set(SyntaxError, new ErrorObjectSerializer(SyntaxError));
  113. jsTypes.set(TypeError, new ErrorObjectSerializer(TypeError));
  114. // If in a sandboxed environment (e. g. jest), this escapes the sandbox and registers
  115. // real Object and Array types to. These types may occur in the wild too, e. g. when
  116. // using Structured Clone in postMessage.
  117. if (exports.constructor !== Object) {
  118. // eslint-disable-next-line jsdoc/check-types
  119. const Obj = /** @type {typeof Object} */ (exports.constructor);
  120. const Fn = /** @type {typeof Function} */ (Obj.constructor);
  121. for (const [type, config] of Array.from(jsTypes)) {
  122. if (type) {
  123. const Type = new Fn(`return ${type.name};`)();
  124. jsTypes.set(Type, config);
  125. }
  126. }
  127. }
  128. {
  129. let i = 1;
  130. for (const [type, serializer] of jsTypes) {
  131. serializers.set(type, {
  132. request: "",
  133. name: i++,
  134. serializer
  135. });
  136. }
  137. }
  138. for (const { request, name, serializer } of serializers.values()) {
  139. serializerInversed.set(
  140. `${request}/${name}`,
  141. /** @type {ObjectSerializer} */ (serializer)
  142. );
  143. }
  144. /** @type {Map<RegExp, (request: string) => boolean>} */
  145. const loaders = new Map();
  146. /**
  147. * @typedef {ComplexSerializableType[]} DeserializedType
  148. * @typedef {PrimitiveSerializableType[]} SerializedType
  149. * @extends {SerializerMiddleware<DeserializedType, SerializedType>}
  150. */
  151. class ObjectMiddleware extends SerializerMiddleware {
  152. /**
  153. * @param {function(any): void} extendContext context extensions
  154. * @param {string | Hash} hashFunction hash function to use
  155. */
  156. constructor(extendContext, hashFunction = "md4") {
  157. super();
  158. this.extendContext = extendContext;
  159. this._hashFunction = hashFunction;
  160. }
  161. /**
  162. * @param {RegExp} regExp RegExp for which the request is tested
  163. * @param {function(string): boolean} loader loader to load the request, returns true when successful
  164. * @returns {void}
  165. */
  166. static registerLoader(regExp, loader) {
  167. loaders.set(regExp, loader);
  168. }
  169. /**
  170. * @param {Constructor} Constructor the constructor
  171. * @param {string} request the request which will be required when deserializing
  172. * @param {string | null} name the name to make multiple serializer unique when sharing a request
  173. * @param {ObjectSerializer} serializer the serializer
  174. * @returns {void}
  175. */
  176. static register(Constructor, request, name, serializer) {
  177. const key = request + "/" + name;
  178. if (serializers.has(Constructor)) {
  179. throw new Error(
  180. `ObjectMiddleware.register: serializer for ${Constructor.name} is already registered`
  181. );
  182. }
  183. if (serializerInversed.has(key)) {
  184. throw new Error(
  185. `ObjectMiddleware.register: serializer for ${key} is already registered`
  186. );
  187. }
  188. serializers.set(Constructor, {
  189. request,
  190. name,
  191. serializer
  192. });
  193. serializerInversed.set(key, serializer);
  194. }
  195. /**
  196. * @param {Constructor} Constructor the constructor
  197. * @returns {void}
  198. */
  199. static registerNotSerializable(Constructor) {
  200. if (serializers.has(Constructor)) {
  201. throw new Error(
  202. `ObjectMiddleware.registerNotSerializable: serializer for ${Constructor.name} is already registered`
  203. );
  204. }
  205. serializers.set(Constructor, NOT_SERIALIZABLE);
  206. }
  207. static getSerializerFor(object) {
  208. const proto = Object.getPrototypeOf(object);
  209. let c;
  210. if (proto === null) {
  211. // Object created with Object.create(null)
  212. c = null;
  213. } else {
  214. c = proto.constructor;
  215. if (!c) {
  216. throw new Error(
  217. "Serialization of objects with prototype without valid constructor property not possible"
  218. );
  219. }
  220. }
  221. const config = serializers.get(c);
  222. if (!config) throw new Error(`No serializer registered for ${c.name}`);
  223. if (config === NOT_SERIALIZABLE) throw NOT_SERIALIZABLE;
  224. return config;
  225. }
  226. /**
  227. * @param {string} request request
  228. * @param {TODO} name name
  229. * @returns {ObjectSerializer} serializer
  230. */
  231. static getDeserializerFor(request, name) {
  232. const key = request + "/" + name;
  233. const serializer = serializerInversed.get(key);
  234. if (serializer === undefined) {
  235. throw new Error(`No deserializer registered for ${key}`);
  236. }
  237. return serializer;
  238. }
  239. /**
  240. * @param {string} request request
  241. * @param {TODO} name name
  242. * @returns {ObjectSerializer} serializer
  243. */
  244. static _getDeserializerForWithoutError(request, name) {
  245. const key = request + "/" + name;
  246. const serializer = serializerInversed.get(key);
  247. return serializer;
  248. }
  249. /**
  250. * @param {DeserializedType} data data
  251. * @param {object} context context object
  252. * @returns {SerializedType|Promise<SerializedType>} serialized data
  253. */
  254. serialize(data, context) {
  255. /** @type {any[]} */
  256. let result = [CURRENT_VERSION];
  257. let currentPos = 0;
  258. let referenceable = new Map();
  259. const addReferenceable = item => {
  260. referenceable.set(item, currentPos++);
  261. };
  262. let bufferDedupeMap = new Map();
  263. const dedupeBuffer = buf => {
  264. const len = buf.length;
  265. const entry = bufferDedupeMap.get(len);
  266. if (entry === undefined) {
  267. bufferDedupeMap.set(len, buf);
  268. return buf;
  269. }
  270. if (Buffer.isBuffer(entry)) {
  271. if (len < 32) {
  272. if (buf.equals(entry)) {
  273. return entry;
  274. }
  275. bufferDedupeMap.set(len, [entry, buf]);
  276. return buf;
  277. } else {
  278. const hash = toHash(entry, this._hashFunction);
  279. const newMap = new Map();
  280. newMap.set(hash, entry);
  281. bufferDedupeMap.set(len, newMap);
  282. const hashBuf = toHash(buf, this._hashFunction);
  283. if (hash === hashBuf) {
  284. return entry;
  285. }
  286. return buf;
  287. }
  288. } else if (Array.isArray(entry)) {
  289. if (entry.length < 16) {
  290. for (const item of entry) {
  291. if (buf.equals(item)) {
  292. return item;
  293. }
  294. }
  295. entry.push(buf);
  296. return buf;
  297. } else {
  298. const newMap = new Map();
  299. const hash = toHash(buf, this._hashFunction);
  300. let found;
  301. for (const item of entry) {
  302. const itemHash = toHash(item, this._hashFunction);
  303. newMap.set(itemHash, item);
  304. if (found === undefined && itemHash === hash) found = item;
  305. }
  306. bufferDedupeMap.set(len, newMap);
  307. if (found === undefined) {
  308. newMap.set(hash, buf);
  309. return buf;
  310. } else {
  311. return found;
  312. }
  313. }
  314. } else {
  315. const hash = toHash(buf, this._hashFunction);
  316. const item = entry.get(hash);
  317. if (item !== undefined) {
  318. return item;
  319. }
  320. entry.set(hash, buf);
  321. return buf;
  322. }
  323. };
  324. let currentPosTypeLookup = 0;
  325. let objectTypeLookup = new Map();
  326. const cycleStack = new Set();
  327. const stackToString = item => {
  328. const arr = Array.from(cycleStack);
  329. arr.push(item);
  330. return arr
  331. .map(item => {
  332. if (typeof item === "string") {
  333. if (item.length > 100) {
  334. return `String ${JSON.stringify(item.slice(0, 100)).slice(
  335. 0,
  336. -1
  337. )}..."`;
  338. }
  339. return `String ${JSON.stringify(item)}`;
  340. }
  341. try {
  342. const { request, name } = ObjectMiddleware.getSerializerFor(item);
  343. if (request) {
  344. return `${request}${name ? `.${name}` : ""}`;
  345. }
  346. } catch (e) {
  347. // ignore -> fallback
  348. }
  349. if (typeof item === "object" && item !== null) {
  350. if (item.constructor) {
  351. if (item.constructor === Object)
  352. return `Object { ${Object.keys(item).join(", ")} }`;
  353. if (item.constructor === Map) return `Map { ${item.size} items }`;
  354. if (item.constructor === Array)
  355. return `Array { ${item.length} items }`;
  356. if (item.constructor === Set) return `Set { ${item.size} items }`;
  357. if (item.constructor === RegExp) return item.toString();
  358. return `${item.constructor.name}`;
  359. }
  360. return `Object [null prototype] { ${Object.keys(item).join(
  361. ", "
  362. )} }`;
  363. }
  364. if (typeof item === "bigint") {
  365. return `BigInt ${item}n`;
  366. }
  367. try {
  368. return `${item}`;
  369. } catch (e) {
  370. return `(${e.message})`;
  371. }
  372. })
  373. .join(" -> ");
  374. };
  375. let hasDebugInfoAttached;
  376. let ctx = {
  377. write(value, key) {
  378. try {
  379. process(value);
  380. } catch (e) {
  381. if (e !== NOT_SERIALIZABLE) {
  382. if (hasDebugInfoAttached === undefined)
  383. hasDebugInfoAttached = new WeakSet();
  384. if (!hasDebugInfoAttached.has(e)) {
  385. e.message += `\nwhile serializing ${stackToString(value)}`;
  386. hasDebugInfoAttached.add(e);
  387. }
  388. }
  389. throw e;
  390. }
  391. },
  392. setCircularReference(ref) {
  393. addReferenceable(ref);
  394. },
  395. snapshot() {
  396. return {
  397. length: result.length,
  398. cycleStackSize: cycleStack.size,
  399. referenceableSize: referenceable.size,
  400. currentPos,
  401. objectTypeLookupSize: objectTypeLookup.size,
  402. currentPosTypeLookup
  403. };
  404. },
  405. rollback(snapshot) {
  406. result.length = snapshot.length;
  407. setSetSize(cycleStack, snapshot.cycleStackSize);
  408. setMapSize(referenceable, snapshot.referenceableSize);
  409. currentPos = snapshot.currentPos;
  410. setMapSize(objectTypeLookup, snapshot.objectTypeLookupSize);
  411. currentPosTypeLookup = snapshot.currentPosTypeLookup;
  412. },
  413. ...context
  414. };
  415. this.extendContext(ctx);
  416. const process = item => {
  417. if (Buffer.isBuffer(item)) {
  418. // check if we can emit a reference
  419. const ref = referenceable.get(item);
  420. if (ref !== undefined) {
  421. result.push(ESCAPE, ref - currentPos);
  422. return;
  423. }
  424. const alreadyUsedBuffer = dedupeBuffer(item);
  425. if (alreadyUsedBuffer !== item) {
  426. const ref = referenceable.get(alreadyUsedBuffer);
  427. if (ref !== undefined) {
  428. referenceable.set(item, ref);
  429. result.push(ESCAPE, ref - currentPos);
  430. return;
  431. }
  432. item = alreadyUsedBuffer;
  433. }
  434. addReferenceable(item);
  435. result.push(item);
  436. } else if (item === ESCAPE) {
  437. result.push(ESCAPE, ESCAPE_ESCAPE_VALUE);
  438. } else if (
  439. typeof item === "object"
  440. // We don't have to check for null as ESCAPE is null and this has been checked before
  441. ) {
  442. // check if we can emit a reference
  443. const ref = referenceable.get(item);
  444. if (ref !== undefined) {
  445. result.push(ESCAPE, ref - currentPos);
  446. return;
  447. }
  448. if (cycleStack.has(item)) {
  449. throw new Error(
  450. `This is a circular references. To serialize circular references use 'setCircularReference' somewhere in the circle during serialize and deserialize.`
  451. );
  452. }
  453. const { request, name, serializer } =
  454. ObjectMiddleware.getSerializerFor(item);
  455. const key = `${request}/${name}`;
  456. const lastIndex = objectTypeLookup.get(key);
  457. if (lastIndex === undefined) {
  458. objectTypeLookup.set(key, currentPosTypeLookup++);
  459. result.push(ESCAPE, request, name);
  460. } else {
  461. result.push(ESCAPE, currentPosTypeLookup - lastIndex);
  462. }
  463. cycleStack.add(item);
  464. try {
  465. serializer.serialize(item, ctx);
  466. } finally {
  467. cycleStack.delete(item);
  468. }
  469. result.push(ESCAPE, ESCAPE_END_OBJECT);
  470. addReferenceable(item);
  471. } else if (typeof item === "string") {
  472. if (item.length > 1) {
  473. // short strings are shorter when not emitting a reference (this saves 1 byte per empty string)
  474. // check if we can emit a reference
  475. const ref = referenceable.get(item);
  476. if (ref !== undefined) {
  477. result.push(ESCAPE, ref - currentPos);
  478. return;
  479. }
  480. addReferenceable(item);
  481. }
  482. if (item.length > 102400 && context.logger) {
  483. context.logger.warn(
  484. `Serializing big strings (${Math.round(
  485. item.length / 1024
  486. )}kiB) impacts deserialization performance (consider using Buffer instead and decode when needed)`
  487. );
  488. }
  489. result.push(item);
  490. } else if (typeof item === "function") {
  491. if (!SerializerMiddleware.isLazy(item))
  492. throw new Error("Unexpected function " + item);
  493. /** @type {SerializedType} */
  494. const serializedData =
  495. SerializerMiddleware.getLazySerializedValue(item);
  496. if (serializedData !== undefined) {
  497. if (typeof serializedData === "function") {
  498. result.push(serializedData);
  499. } else {
  500. throw new Error("Not implemented");
  501. }
  502. } else if (SerializerMiddleware.isLazy(item, this)) {
  503. throw new Error("Not implemented");
  504. } else {
  505. const data = SerializerMiddleware.serializeLazy(item, data =>
  506. this.serialize([data], context)
  507. );
  508. SerializerMiddleware.setLazySerializedValue(item, data);
  509. result.push(data);
  510. }
  511. } else if (item === undefined) {
  512. result.push(ESCAPE, ESCAPE_UNDEFINED);
  513. } else {
  514. result.push(item);
  515. }
  516. };
  517. try {
  518. for (const item of data) {
  519. process(item);
  520. }
  521. return result;
  522. } catch (e) {
  523. if (e === NOT_SERIALIZABLE) return null;
  524. throw e;
  525. } finally {
  526. // Get rid of these references to avoid leaking memory
  527. // This happens because the optimized code v8 generates
  528. // is optimized for our "ctx.write" method so it will reference
  529. // it from e. g. Dependency.prototype.serialize -(IC)-> ctx.write
  530. data =
  531. result =
  532. referenceable =
  533. bufferDedupeMap =
  534. objectTypeLookup =
  535. ctx =
  536. undefined;
  537. }
  538. }
  539. /**
  540. * @param {SerializedType} data data
  541. * @param {object} context context object
  542. * @returns {DeserializedType|Promise<DeserializedType>} deserialized data
  543. */
  544. deserialize(data, context) {
  545. let currentDataPos = 0;
  546. const read = () => {
  547. if (currentDataPos >= data.length)
  548. throw new Error("Unexpected end of stream");
  549. return data[currentDataPos++];
  550. };
  551. if (read() !== CURRENT_VERSION)
  552. throw new Error("Version mismatch, serializer changed");
  553. let currentPos = 0;
  554. let referenceable = [];
  555. const addReferenceable = item => {
  556. referenceable.push(item);
  557. currentPos++;
  558. };
  559. let currentPosTypeLookup = 0;
  560. let objectTypeLookup = [];
  561. let result = [];
  562. let ctx = {
  563. read() {
  564. return decodeValue();
  565. },
  566. setCircularReference(ref) {
  567. addReferenceable(ref);
  568. },
  569. ...context
  570. };
  571. this.extendContext(ctx);
  572. const decodeValue = () => {
  573. const item = read();
  574. if (item === ESCAPE) {
  575. const nextItem = read();
  576. if (nextItem === ESCAPE_ESCAPE_VALUE) {
  577. return ESCAPE;
  578. } else if (nextItem === ESCAPE_UNDEFINED) {
  579. return undefined;
  580. } else if (nextItem === ESCAPE_END_OBJECT) {
  581. throw new Error(
  582. `Unexpected end of object at position ${currentDataPos - 1}`
  583. );
  584. } else {
  585. const request = nextItem;
  586. let serializer;
  587. if (typeof request === "number") {
  588. if (request < 0) {
  589. // relative reference
  590. return referenceable[currentPos + request];
  591. }
  592. serializer = objectTypeLookup[currentPosTypeLookup - request];
  593. } else {
  594. if (typeof request !== "string") {
  595. throw new Error(
  596. `Unexpected type (${typeof request}) of request ` +
  597. `at position ${currentDataPos - 1}`
  598. );
  599. }
  600. const name = read();
  601. serializer = ObjectMiddleware._getDeserializerForWithoutError(
  602. request,
  603. name
  604. );
  605. if (serializer === undefined) {
  606. if (request && !loadedRequests.has(request)) {
  607. let loaded = false;
  608. for (const [regExp, loader] of loaders) {
  609. if (regExp.test(request)) {
  610. if (loader(request)) {
  611. loaded = true;
  612. break;
  613. }
  614. }
  615. }
  616. if (!loaded) {
  617. require(request);
  618. }
  619. loadedRequests.add(request);
  620. }
  621. serializer = ObjectMiddleware.getDeserializerFor(request, name);
  622. }
  623. objectTypeLookup.push(serializer);
  624. currentPosTypeLookup++;
  625. }
  626. try {
  627. const item = serializer.deserialize(ctx);
  628. const end1 = read();
  629. if (end1 !== ESCAPE) {
  630. throw new Error("Expected end of object");
  631. }
  632. const end2 = read();
  633. if (end2 !== ESCAPE_END_OBJECT) {
  634. throw new Error("Expected end of object");
  635. }
  636. addReferenceable(item);
  637. return item;
  638. } catch (err) {
  639. // As this is only for error handling, we omit creating a Map for
  640. // faster access to this information, as this would affect performance
  641. // in the good case
  642. let serializerEntry;
  643. for (const entry of serializers) {
  644. if (entry[1].serializer === serializer) {
  645. serializerEntry = entry;
  646. break;
  647. }
  648. }
  649. const name = !serializerEntry
  650. ? "unknown"
  651. : !serializerEntry[1].request
  652. ? serializerEntry[0].name
  653. : serializerEntry[1].name
  654. ? `${serializerEntry[1].request} ${serializerEntry[1].name}`
  655. : serializerEntry[1].request;
  656. err.message += `\n(during deserialization of ${name})`;
  657. throw err;
  658. }
  659. }
  660. } else if (typeof item === "string") {
  661. if (item.length > 1) {
  662. addReferenceable(item);
  663. }
  664. return item;
  665. } else if (Buffer.isBuffer(item)) {
  666. addReferenceable(item);
  667. return item;
  668. } else if (typeof item === "function") {
  669. return SerializerMiddleware.deserializeLazy(
  670. item,
  671. data => this.deserialize(data, context)[0]
  672. );
  673. } else {
  674. return item;
  675. }
  676. };
  677. try {
  678. while (currentDataPos < data.length) {
  679. result.push(decodeValue());
  680. }
  681. return result;
  682. } finally {
  683. // Get rid of these references to avoid leaking memory
  684. // This happens because the optimized code v8 generates
  685. // is optimized for our "ctx.read" method so it will reference
  686. // it from e. g. Dependency.prototype.deserialize -(IC)-> ctx.read
  687. result = referenceable = data = objectTypeLookup = ctx = undefined;
  688. }
  689. }
  690. }
  691. module.exports = ObjectMiddleware;
  692. module.exports.NOT_SERIALIZABLE = NOT_SERIALIZABLE;