Skip to content

Commit 918d470

Browse files
lforstAbhiPrasad
authored andcommitted
fix(utils): Fix infinite recursion in dropUndefinedKeys (#5163)
1 parent bc73dc0 commit 918d470

File tree

2 files changed

+61
-2
lines changed

2 files changed

+61
-2
lines changed

packages/utils/src/object.ts

+20-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { WrappedFunction } from '@sentry/types';
44

55
import { htmlTreeAsString } from './browser';
66
import { isElement, isError, isEvent, isInstanceOf, isPlainObject, isPrimitive } from './is';
7+
import { memoBuilder, MemoFunc } from './memo';
78
import { truncate } from './string';
89

910
/**
@@ -205,20 +206,37 @@ export function extractExceptionKeysForMessage(exception: Record<string, unknown
205206
/**
206207
* Given any object, return the new object with removed keys that value was `undefined`.
207208
* Works recursively on objects and arrays.
209+
*
210+
* Attention: This function keeps circular references in the returned object.
208211
*/
209212
export function dropUndefinedKeys<T>(val: T): T {
213+
// This function just proxies `_dropUndefinedKeys` to keep the `memoBuilder` out of this function's API
214+
return _dropUndefinedKeys(val, memoBuilder());
215+
}
216+
217+
function _dropUndefinedKeys<T>(val: T, memo: MemoFunc): T {
218+
const [memoize] = memo; // we don't need unmemoize because we don't need to visit nodes twice
219+
210220
if (isPlainObject(val)) {
221+
if (memoize(val)) {
222+
return val;
223+
}
211224
const rv: { [key: string]: any } = {};
212225
for (const key of Object.keys(val)) {
213226
if (typeof val[key] !== 'undefined') {
214-
rv[key] = dropUndefinedKeys(val[key]);
227+
rv[key] = _dropUndefinedKeys(val[key], memo);
215228
}
216229
}
217230
return rv as T;
218231
}
219232

220233
if (Array.isArray(val)) {
221-
return (val as any[]).map(dropUndefinedKeys) as any;
234+
if (memoize(val)) {
235+
return val;
236+
}
237+
return (val as any[]).map(item => {
238+
return _dropUndefinedKeys(item, memo);
239+
}) as any;
222240
}
223241

224242
return val;

packages/utils/test/object.test.ts

+41
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,47 @@ describe('dropUndefinedKeys()', () => {
199199
},
200200
});
201201
});
202+
203+
test('objects with circular reference', () => {
204+
const dog: any = {
205+
food: undefined,
206+
};
207+
208+
const human = {
209+
brain: undefined,
210+
pets: dog,
211+
};
212+
213+
const rat = {
214+
scares: human,
215+
weight: '4kg',
216+
};
217+
218+
dog.chases = rat;
219+
220+
expect(dropUndefinedKeys(human)).toStrictEqual({
221+
pets: {
222+
chases: rat,
223+
},
224+
});
225+
});
226+
227+
test('arrays with circular reference', () => {
228+
const egg: any[] = [];
229+
230+
const chicken = {
231+
food: undefined,
232+
weight: '1kg',
233+
lays: egg,
234+
};
235+
236+
egg[0] = chicken;
237+
238+
expect(dropUndefinedKeys(chicken)).toStrictEqual({
239+
lays: egg,
240+
weight: '1kg',
241+
});
242+
});
202243
});
203244

204245
describe('objectify()', () => {

0 commit comments

Comments
 (0)