source: imaps-frontend/node_modules/webpack/lib/serialization/ObjectMiddleware.js

main
Last change on this file was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 4 days ago

F4 Finalna Verzija

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