source: trip-planner-front/node_modules/webpack/lib/serialization/ObjectMiddleware.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 19.3 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 {import("./types").ComplexSerializableType} ComplexSerializableType */
19/** @typedef {import("./types").PrimitiveSerializableType} PrimitiveSerializableType */
20
21/** @typedef {new (...params: any[]) => any} Constructor */
22
23/*
24
25Format:
26
27File -> Section*
28Section -> ObjectSection | ReferenceSection | EscapeSection | OtherSection
29
30ObjectSection -> ESCAPE (
31 number:relativeOffset (number > 0) |
32 string:request (string|null):export
33) Section:value* ESCAPE ESCAPE_END_OBJECT
34ReferenceSection -> ESCAPE number:relativeOffset (number < 0)
35EscapeSection -> ESCAPE ESCAPE_ESCAPE_VALUE (escaped value ESCAPE)
36EscapeSection -> ESCAPE ESCAPE_UNDEFINED (escaped value ESCAPE)
37OtherSection -> any (except ESCAPE)
38
39Why using null as escape value?
40Multiple null values can merged by the BinaryMiddleware, which makes it very efficient
41Technically any value can be used.
42
43*/
44
45/**
46 * @typedef {Object} ObjectSerializerContext
47 * @property {function(any): void} write
48 */
49
50/**
51 * @typedef {Object} ObjectDeserializerContext
52 * @property {function(): any} read
53 */
54
55/**
56 * @typedef {Object} ObjectSerializer
57 * @property {function(any, ObjectSerializerContext): void} serialize
58 * @property {function(ObjectDeserializerContext): any} deserialize
59 */
60
61const setSetSize = (set, size) => {
62 let i = 0;
63 for (const item of set) {
64 if (i++ >= size) {
65 set.delete(item);
66 }
67 }
68};
69
70const 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
79const toHash = buffer => {
80 const hash = createHash("md4");
81 hash.update(buffer);
82 return /** @type {string} */ (hash.digest("latin1"));
83};
84
85const ESCAPE = null;
86const ESCAPE_ESCAPE_VALUE = null;
87const ESCAPE_END_OBJECT = true;
88const ESCAPE_UNDEFINED = false;
89
90const CURRENT_VERSION = 2;
91
92const serializers = new Map();
93const serializerInversed = new Map();
94
95const loadedRequests = new Set();
96
97const NOT_SERIALIZABLE = {};
98
99const jsTypes = new Map();
100jsTypes.set(Object, new PlainObjectSerializer());
101jsTypes.set(Array, new ArraySerializer());
102jsTypes.set(null, new NullPrototypeObjectSerializer());
103jsTypes.set(Map, new MapObjectSerializer());
104jsTypes.set(Set, new SetObjectSerializer());
105jsTypes.set(Date, new DateObjectSerializer());
106jsTypes.set(RegExp, new RegExpObjectSerializer());
107jsTypes.set(Error, new ErrorObjectSerializer(Error));
108jsTypes.set(EvalError, new ErrorObjectSerializer(EvalError));
109jsTypes.set(RangeError, new ErrorObjectSerializer(RangeError));
110jsTypes.set(ReferenceError, new ErrorObjectSerializer(ReferenceError));
111jsTypes.set(SyntaxError, new ErrorObjectSerializer(SyntaxError));
112jsTypes.set(TypeError, new ErrorObjectSerializer(TypeError));
113
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.
117if (exports.constructor !== Object) {
118 const Obj = /** @type {typeof Object} */ (exports.constructor);
119 const Fn = /** @type {typeof Function} */ (Obj.constructor);
120 for (const [type, config] of Array.from(jsTypes)) {
121 if (type) {
122 const Type = new Fn(`return ${type.name};`)();
123 jsTypes.set(Type, config);
124 }
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
139for (const { request, name, serializer } of serializers.values()) {
140 serializerInversed.set(`${request}/${name}`, serializer);
141}
142
143/** @type {Map<RegExp, (request: string) => boolean>} */
144const loaders = new Map();
145
146/**
147 * @typedef {ComplexSerializableType[]} DeserializedType
148 * @typedef {PrimitiveSerializableType[]} SerializedType
149 * @extends {SerializerMiddleware<DeserializedType, SerializedType>}
150 */
151class ObjectMiddleware extends SerializerMiddleware {
152 constructor(extendContext) {
153 super();
154 this.extendContext = extendContext;
155 }
156 /**
157 * @param {RegExp} regExp RegExp for which the request is tested
158 * @param {function(string): boolean} loader loader to load the request, returns true when successful
159 * @returns {void}
160 */
161 static registerLoader(regExp, loader) {
162 loaders.set(regExp, loader);
163 }
164
165 /**
166 * @param {Constructor} Constructor the constructor
167 * @param {string} request the request which will be required when deserializing
168 * @param {string} name the name to make multiple serializer unique when sharing a request
169 * @param {ObjectSerializer} serializer the serializer
170 * @returns {void}
171 */
172 static register(Constructor, request, name, serializer) {
173 const key = request + "/" + name;
174
175 if (serializers.has(Constructor)) {
176 throw new Error(
177 `ObjectMiddleware.register: serializer for ${Constructor.name} is already registered`
178 );
179 }
180
181 if (serializerInversed.has(key)) {
182 throw new Error(
183 `ObjectMiddleware.register: serializer for ${key} is already registered`
184 );
185 }
186
187 serializers.set(Constructor, {
188 request,
189 name,
190 serializer
191 });
192
193 serializerInversed.set(key, serializer);
194 }
195
196 /**
197 * @param {Constructor} Constructor the constructor
198 * @returns {void}
199 */
200 static registerNotSerializable(Constructor) {
201 if (serializers.has(Constructor)) {
202 throw new Error(
203 `ObjectMiddleware.registerNotSerializable: serializer for ${Constructor.name} is already registered`
204 );
205 }
206
207 serializers.set(Constructor, NOT_SERIALIZABLE);
208 }
209
210 static getSerializerFor(object) {
211 const proto = Object.getPrototypeOf(object);
212 let c;
213 if (proto === null) {
214 // Object created with Object.create(null)
215 c = null;
216 } else {
217 c = proto.constructor;
218 if (!c) {
219 throw new Error(
220 "Serialization of objects with prototype without valid constructor property not possible"
221 );
222 }
223 }
224 const config = serializers.get(c);
225
226 if (!config) throw new Error(`No serializer registered for ${c.name}`);
227 if (config === NOT_SERIALIZABLE) throw NOT_SERIALIZABLE;
228
229 return config;
230 }
231
232 static getDeserializerFor(request, name) {
233 const key = request + "/" + name;
234 const serializer = serializerInversed.get(key);
235
236 if (serializer === undefined) {
237 throw new Error(`No deserializer registered for ${key}`);
238 }
239
240 return serializer;
241 }
242
243 static _getDeserializerForWithoutError(request, name) {
244 const key = request + "/" + name;
245 const serializer = serializerInversed.get(key);
246 return serializer;
247 }
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);
279 const newMap = new Map();
280 newMap.set(hash, entry);
281 bufferDedupeMap.set(len, newMap);
282 const hashBuf = toHash(buf);
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);
300 let found;
301 for (const item of entry) {
302 const itemHash = toHash(item);
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);
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 try {
365 return `${item}`;
366 } catch (e) {
367 return `(${e.message})`;
368 }
369 })
370 .join(" -> ");
371 };
372 let hasDebugInfoAttached;
373 let ctx = {
374 write(value, key) {
375 try {
376 process(value);
377 } catch (e) {
378 if (e !== NOT_SERIALIZABLE) {
379 if (hasDebugInfoAttached === undefined)
380 hasDebugInfoAttached = new WeakSet();
381 if (!hasDebugInfoAttached.has(e)) {
382 e.message += `\nwhile serializing ${stackToString(value)}`;
383 hasDebugInfoAttached.add(e);
384 }
385 }
386 throw e;
387 }
388 },
389 setCircularReference(ref) {
390 addReferenceable(ref);
391 },
392 snapshot() {
393 return {
394 length: result.length,
395 cycleStackSize: cycleStack.size,
396 referenceableSize: referenceable.size,
397 currentPos,
398 objectTypeLookupSize: objectTypeLookup.size,
399 currentPosTypeLookup
400 };
401 },
402 rollback(snapshot) {
403 result.length = snapshot.length;
404 setSetSize(cycleStack, snapshot.cycleStackSize);
405 setMapSize(referenceable, snapshot.referenceableSize);
406 currentPos = snapshot.currentPos;
407 setMapSize(objectTypeLookup, snapshot.objectTypeLookupSize);
408 currentPosTypeLookup = snapshot.currentPosTypeLookup;
409 },
410 ...context
411 };
412 this.extendContext(ctx);
413 const process = item => {
414 if (Buffer.isBuffer(item)) {
415 // check if we can emit a reference
416 const ref = referenceable.get(item);
417 if (ref !== undefined) {
418 result.push(ESCAPE, ref - currentPos);
419 return;
420 }
421 const alreadyUsedBuffer = dedupeBuffer(item);
422 if (alreadyUsedBuffer !== item) {
423 const ref = referenceable.get(alreadyUsedBuffer);
424 if (ref !== undefined) {
425 referenceable.set(item, ref);
426 result.push(ESCAPE, ref - currentPos);
427 return;
428 }
429 item = alreadyUsedBuffer;
430 }
431 addReferenceable(item);
432
433 result.push(item);
434 } else if (item === ESCAPE) {
435 result.push(ESCAPE, ESCAPE_ESCAPE_VALUE);
436 } else if (
437 typeof item === "object"
438 // We don't have to check for null as ESCAPE is null and this has been checked before
439 ) {
440 // check if we can emit a reference
441 const ref = referenceable.get(item);
442 if (ref !== undefined) {
443 result.push(ESCAPE, ref - currentPos);
444 return;
445 }
446
447 if (cycleStack.has(item)) {
448 throw new Error(
449 `This is a circular references. To serialize circular references use 'setCircularReference' somewhere in the circle during serialize and deserialize.`
450 );
451 }
452
453 const { request, name, serializer } =
454 ObjectMiddleware.getSerializerFor(item);
455 const key = `${request}/${name}`;
456 const lastIndex = objectTypeLookup.get(key);
457
458 if (lastIndex === undefined) {
459 objectTypeLookup.set(key, currentPosTypeLookup++);
460
461 result.push(ESCAPE, request, name);
462 } else {
463 result.push(ESCAPE, currentPosTypeLookup - lastIndex);
464 }
465
466 cycleStack.add(item);
467
468 try {
469 serializer.serialize(item, ctx);
470 } finally {
471 cycleStack.delete(item);
472 }
473
474 result.push(ESCAPE, ESCAPE_END_OBJECT);
475
476 addReferenceable(item);
477 } else if (typeof item === "string") {
478 if (item.length > 1) {
479 // short strings are shorter when not emitting a reference (this saves 1 byte per empty string)
480 // check if we can emit a reference
481 const ref = referenceable.get(item);
482 if (ref !== undefined) {
483 result.push(ESCAPE, ref - currentPos);
484 return;
485 }
486 addReferenceable(item);
487 }
488
489 if (item.length > 102400 && context.logger) {
490 context.logger.warn(
491 `Serializing big strings (${Math.round(
492 item.length / 1024
493 )}kiB) impacts deserialization performance (consider using Buffer instead and decode when needed)`
494 );
495 }
496
497 result.push(item);
498 } else if (typeof item === "function") {
499 if (!SerializerMiddleware.isLazy(item))
500 throw new Error("Unexpected function " + item);
501 /** @type {SerializedType} */
502 const serializedData =
503 SerializerMiddleware.getLazySerializedValue(item);
504 if (serializedData !== undefined) {
505 if (typeof serializedData === "function") {
506 result.push(serializedData);
507 } else {
508 throw new Error("Not implemented");
509 }
510 } else if (SerializerMiddleware.isLazy(item, this)) {
511 throw new Error("Not implemented");
512 } else {
513 result.push(
514 SerializerMiddleware.serializeLazy(item, data =>
515 this.serialize([data], context)
516 )
517 );
518 }
519 } else if (item === undefined) {
520 result.push(ESCAPE, ESCAPE_UNDEFINED);
521 } else {
522 result.push(item);
523 }
524 };
525
526 try {
527 for (const item of data) {
528 process(item);
529 }
530 return result;
531 } catch (e) {
532 if (e === NOT_SERIALIZABLE) return null;
533
534 throw e;
535 } finally {
536 // Get rid of these references to avoid leaking memory
537 // This happens because the optimized code v8 generates
538 // is optimized for our "ctx.write" method so it will reference
539 // it from e. g. Dependency.prototype.serialize -(IC)-> ctx.write
540 data =
541 result =
542 referenceable =
543 bufferDedupeMap =
544 objectTypeLookup =
545 ctx =
546 undefined;
547 }
548 }
549
550 /**
551 * @param {SerializedType} data data
552 * @param {Object} context context object
553 * @returns {DeserializedType|Promise<DeserializedType>} deserialized data
554 */
555 deserialize(data, context) {
556 let currentDataPos = 0;
557 const read = () => {
558 if (currentDataPos >= data.length)
559 throw new Error("Unexpected end of stream");
560
561 return data[currentDataPos++];
562 };
563
564 if (read() !== CURRENT_VERSION)
565 throw new Error("Version mismatch, serializer changed");
566
567 let currentPos = 0;
568 let referenceable = [];
569 const addReferenceable = item => {
570 referenceable.push(item);
571 currentPos++;
572 };
573 let currentPosTypeLookup = 0;
574 let objectTypeLookup = [];
575 let result = [];
576 let ctx = {
577 read() {
578 return decodeValue();
579 },
580 setCircularReference(ref) {
581 addReferenceable(ref);
582 },
583 ...context
584 };
585 this.extendContext(ctx);
586 const decodeValue = () => {
587 const item = read();
588
589 if (item === ESCAPE) {
590 const nextItem = read();
591
592 if (nextItem === ESCAPE_ESCAPE_VALUE) {
593 return ESCAPE;
594 } else if (nextItem === ESCAPE_UNDEFINED) {
595 return undefined;
596 } else if (nextItem === ESCAPE_END_OBJECT) {
597 throw new Error(
598 `Unexpected end of object at position ${currentDataPos - 1}`
599 );
600 } else {
601 const request = nextItem;
602 let serializer;
603
604 if (typeof request === "number") {
605 if (request < 0) {
606 // relative reference
607 return referenceable[currentPos + request];
608 }
609 serializer = objectTypeLookup[currentPosTypeLookup - request];
610 } else {
611 if (typeof request !== "string") {
612 throw new Error(
613 `Unexpected type (${typeof request}) of request ` +
614 `at position ${currentDataPos - 1}`
615 );
616 }
617 const name = read();
618
619 serializer = ObjectMiddleware._getDeserializerForWithoutError(
620 request,
621 name
622 );
623
624 if (serializer === undefined) {
625 if (request && !loadedRequests.has(request)) {
626 let loaded = false;
627 for (const [regExp, loader] of loaders) {
628 if (regExp.test(request)) {
629 if (loader(request)) {
630 loaded = true;
631 break;
632 }
633 }
634 }
635 if (!loaded) {
636 require(request);
637 }
638
639 loadedRequests.add(request);
640 }
641
642 serializer = ObjectMiddleware.getDeserializerFor(request, name);
643 }
644
645 objectTypeLookup.push(serializer);
646 currentPosTypeLookup++;
647 }
648 try {
649 const item = serializer.deserialize(ctx);
650 const end1 = read();
651
652 if (end1 !== ESCAPE) {
653 throw new Error("Expected end of object");
654 }
655
656 const end2 = read();
657
658 if (end2 !== ESCAPE_END_OBJECT) {
659 throw new Error("Expected end of object");
660 }
661
662 addReferenceable(item);
663
664 return item;
665 } catch (err) {
666 // As this is only for error handling, we omit creating a Map for
667 // faster access to this information, as this would affect performance
668 // in the good case
669 let serializerEntry;
670 for (const entry of serializers) {
671 if (entry[1].serializer === serializer) {
672 serializerEntry = entry;
673 break;
674 }
675 }
676 const name = !serializerEntry
677 ? "unknown"
678 : !serializerEntry[1].request
679 ? serializerEntry[0].name
680 : serializerEntry[1].name
681 ? `${serializerEntry[1].request} ${serializerEntry[1].name}`
682 : serializerEntry[1].request;
683 err.message += `\n(during deserialization of ${name})`;
684 throw err;
685 }
686 }
687 } else if (typeof item === "string") {
688 if (item.length > 1) {
689 addReferenceable(item);
690 }
691
692 return item;
693 } else if (Buffer.isBuffer(item)) {
694 addReferenceable(item);
695
696 return item;
697 } else if (typeof item === "function") {
698 return SerializerMiddleware.deserializeLazy(
699 item,
700 data => this.deserialize(data, context)[0]
701 );
702 } else {
703 return item;
704 }
705 };
706
707 try {
708 while (currentDataPos < data.length) {
709 result.push(decodeValue());
710 }
711 return result;
712 } finally {
713 // Get rid of these references to avoid leaking memory
714 // This happens because the optimized code v8 generates
715 // is optimized for our "ctx.read" method so it will reference
716 // it from e. g. Dependency.prototype.deserialize -(IC)-> ctx.read
717 result = referenceable = data = objectTypeLookup = ctx = undefined;
718 }
719 }
720}
721
722module.exports = ObjectMiddleware;
723module.exports.NOT_SERIALIZABLE = NOT_SERIALIZABLE;
Note: See TracBrowser for help on using the repository browser.