From c7c6a5b1dde69515e92dbbc250e2624a6769b0e0 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 25 May 2022 09:22:03 +0000 Subject: [PATCH 1/3] fix(utils): Fix infinite recursion in `dropUndefinedKeys` --- packages/utils/src/object.ts | 19 ++++++++++++-- packages/utils/test/object.test.ts | 41 ++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/object.ts b/packages/utils/src/object.ts index d4974cecbf44..0999b38192bc 100644 --- a/packages/utils/src/object.ts +++ b/packages/utils/src/object.ts @@ -4,6 +4,7 @@ import { WrappedFunction } from '@sentry/types'; import { htmlTreeAsString } from './browser'; import { isElement, isError, isEvent, isInstanceOf, isPlainObject, isPrimitive } from './is'; +import { memoBuilder, MemoFunc } from './memo'; import { truncate } from './string'; /** @@ -207,18 +208,32 @@ export function extractExceptionKeysForMessage(exception: Record(val: T): T { + return _dropUndefinedKeys(val, memoBuilder()); +} + +function _dropUndefinedKeys(val: T, memo: MemoFunc): T { + const [memoize] = memo; + if (isPlainObject(val)) { + if (memoize(val)) { + return val; + } const rv: { [key: string]: any } = {}; for (const key of Object.keys(val)) { if (typeof val[key] !== 'undefined') { - rv[key] = dropUndefinedKeys(val[key]); + rv[key] = _dropUndefinedKeys(val[key], memo); } } return rv as T; } if (Array.isArray(val)) { - return (val as any[]).map(dropUndefinedKeys) as any; + if (memoize(val)) { + return val; + } + return (val as any[]).map(item => { + return _dropUndefinedKeys(item, memo); + }) as any; } return val; diff --git a/packages/utils/test/object.test.ts b/packages/utils/test/object.test.ts index e4fc4428ae04..65131fa0cd94 100644 --- a/packages/utils/test/object.test.ts +++ b/packages/utils/test/object.test.ts @@ -199,6 +199,47 @@ describe('dropUndefinedKeys()', () => { }, }); }); + + test('objects with circular reference', () => { + const dog: any = { + food: undefined, + }; + + const human = { + brain: undefined, + pets: dog, + }; + + const rat = { + scares: human, + weight: '4kg', + }; + + dog.chases = rat; + + expect(dropUndefinedKeys(human)).toStrictEqual({ + pets: { + chases: rat, + }, + }); + }); + + test('arrays with circular reference', () => { + const egg: any[] = []; + + const chicken = { + food: undefined, + weight: '1kg', + lays: egg, + }; + + egg[0] = chicken; + + expect(dropUndefinedKeys(chicken)).toStrictEqual({ + lays: egg, + weight: '1kg', + }); + }); }); describe('objectify()', () => { From 75373c15eb8eea2791f91c7c4e898952cedebcea Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 25 May 2022 09:28:23 +0000 Subject: [PATCH 2/3] Add comments --- packages/utils/src/object.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/object.ts b/packages/utils/src/object.ts index 0999b38192bc..e9373797c44f 100644 --- a/packages/utils/src/object.ts +++ b/packages/utils/src/object.ts @@ -208,11 +208,12 @@ export function extractExceptionKeysForMessage(exception: Record(val: T): T { + // This function just proxies `_dropUndefinedKeys` to keep the `memoBuilder` out of this function's API return _dropUndefinedKeys(val, memoBuilder()); } function _dropUndefinedKeys(val: T, memo: MemoFunc): T { - const [memoize] = memo; + const [memoize] = memo; // we don't need unmemoize because we don't need to visit nodes twice if (isPlainObject(val)) { if (memoize(val)) { From 4429b6433acad1481dbe0b8573cb6e4522ba2f0c Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 25 May 2022 10:22:31 +0000 Subject: [PATCH 3/3] Add comment --- packages/utils/src/object.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/utils/src/object.ts b/packages/utils/src/object.ts index e9373797c44f..5957214ecc86 100644 --- a/packages/utils/src/object.ts +++ b/packages/utils/src/object.ts @@ -206,6 +206,8 @@ export function extractExceptionKeysForMessage(exception: Record(val: T): T { // This function just proxies `_dropUndefinedKeys` to keep the `memoBuilder` out of this function's API