From ca60b0e203d950605506eb7687a10b5c87b4492f Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Fri, 28 Oct 2022 15:25:44 +0200 Subject: [PATCH] fix(core): updating deepCopy to avoid max callstack with circular deps (#4468) * fix(core): updating deepCopy to avoid max callstack in case of circular dep * fix(core): show warning with path added to circular reference --- packages/workflow/src/utils.ts | 15 ++++++++---- packages/workflow/test/utils.test.ts | 36 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/packages/workflow/src/utils.ts b/packages/workflow/src/utils.ts index c8694c801f..ddd9e14128 100644 --- a/packages/workflow/src/utils.ts +++ b/packages/workflow/src/utils.ts @@ -1,12 +1,16 @@ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */ -export const deepCopy = (source: T): T => { +export const deepCopy = (source: T, hash = new WeakMap(), path = ''): T => { let clone: any; let i: any; const hasOwnProp = Object.prototype.hasOwnProperty.bind(source); - // Primitives & Null - if (typeof source !== 'object' || source === null) { + // Primitives & Null & Function + if (typeof source !== 'object' || source === null || source instanceof Function) { return source; } + if (hash.has(source)) { + console.warn(`Circular reference detected at "source${path}"`); + return hash.get(source); + } // Date if (source instanceof Date) { return new Date(source.getTime()) as T; @@ -16,15 +20,16 @@ export const deepCopy = (source: T): T => { clone = []; const len = source.length; for (i = 0; i < len; i++) { - clone[i] = deepCopy(source[i]); + clone[i] = deepCopy(source[i], hash, path + `[${i as string}]`); } return clone; } // Object clone = {}; + hash.set(source, clone); for (i in source) { if (hasOwnProp(i)) { - clone[i] = deepCopy((source as any)[i]); + clone[i] = deepCopy((source as any)[i], hash, path + `.${i as string}`); } } return clone; diff --git a/packages/workflow/test/utils.test.ts b/packages/workflow/test/utils.test.ts index 6ec35877b8..bd83309bba 100644 --- a/packages/workflow/test/utils.test.ts +++ b/packages/workflow/test/utils.test.ts @@ -48,4 +48,40 @@ describe('deepCopy', () => { expect(copy.deep.props).toEqual(object.deep.props); expect(copy.deep.props).not.toBe(object.deep.props); }); + + it('should avoid max call stack in case of circular deps', () => { + const object: Record = { + deep: { + props: { + list: [{ a: 1 }, { b: 2 }, { c: 3 }], + }, + arr: [1, 2, 3], + }, + arr: [ + { + prop: { + list: ['a', 'b', 'c'], + }, + }, + ], + func: () => {}, + date: new Date(), + undef: undefined, + nil: null, + bool: true, + num: 1, + }; + + object.circular = object; + object.deep.props.circular = object; + object.deep.arr.push(object) + + const copy = deepCopy(object); + expect(copy).toEqual(object); + expect(copy).not.toBe(object); + expect(copy.arr).toEqual(object.arr); + expect(copy.arr).not.toBe(object.arr); + expect(copy.deep.props).toEqual(object.deep.props); + expect(copy.deep.props).not.toBe(object.deep.props); + }); });