Skip to content

Commit 26093ac

Browse files
committed
ref(utils): Add property flag to skip normalization
1 parent 1e210fe commit 26093ac

File tree

2 files changed

+65
-6
lines changed

2 files changed

+65
-6
lines changed

packages/utils/src/normalize.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type ObjOrArray<T> = { [key: string]: T };
3333
*/
3434
export function normalize(input: unknown, depth: number = +Infinity, maxProperties: number = +Infinity): any {
3535
try {
36-
// since we're at the outermost level, there is no key
36+
// since we're at the outermost level, we don't provide a key
3737
return visit('', input, depth, maxProperties);
3838
} catch (err) {
3939
return { ERROR: `**non-serializable** (${err})` };
@@ -98,17 +98,27 @@ function visit(
9898
return stringified;
9999
}
100100

101-
// We're also done if we've reached the max depth
102-
if (depth === 0) {
103-
// At this point we know `serialized` is a string of the form `"[object XXXX]"`. Clean it up so it's just `"[XXXX]"`.
104-
return stringified.replace('object ', '');
105-
}
101+
// From here on, we can assert that `value` is either an object or an array!
106102

107103
// If we've already visited this branch, bail out, as it's circular reference. If not, note that we're seeing it now.
108104
if (memoize(value)) {
109105
return '[Circular ~]';
110106
}
111107

108+
// Do not normalize objects that we know have already been normalized. This MUST be below the circ-ref check otherwise
109+
// we might somehow accidentally produce circular references by skipping normalization.
110+
// As a general rule, the "__sentry_skip_normalization__" property should only be used sparingly and only should only
111+
// be set on objects that have already been normalized.
112+
if ((value as ObjOrArray<unknown>)['__sentry_skip_normalization__']) {
113+
return value as ObjOrArray<unknown>;
114+
}
115+
116+
// We're also done if we've reached the max depth
117+
if (depth === 0) {
118+
// At this point we know `serialized` is a string of the form `"[object XXXX]"`. Clean it up so it's just `"[XXXX]"`.
119+
return stringified.replace('object ', '');
120+
}
121+
112122
// At this point we know we either have an object or an array, we haven't seen it before, and we're going to recurse
113123
// because we haven't yet reached the max depth. Create an accumulator to hold the results of visiting each
114124
// property/entry, and keep track of the number of items we add to it.

packages/utils/test/normalize.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import * as isModule from '../src/is';
66
import { normalize } from '../src/normalize';
7+
import { addNonEnumerableProperty } from '../src/object';
78
import * as stacktraceModule from '../src/stacktrace';
89

910
describe('normalize()', () => {
@@ -504,4 +505,52 @@ describe('normalize()', () => {
504505
qux: '[Function: qux]',
505506
});
506507
});
508+
509+
describe.only('skips normalizing objects marked with a non-enumerable property __sentry_skip_normalization__', () => {
510+
test('by leaving non-serializable values intact', () => {
511+
const someFun = () => undefined;
512+
const alreadyNormalizedObj = {
513+
nan: NaN,
514+
fun: someFun,
515+
};
516+
517+
addNonEnumerableProperty(alreadyNormalizedObj, '__sentry_skip_normalization__', true);
518+
519+
const result = normalize(alreadyNormalizedObj);
520+
expect(result).toEqual({
521+
nan: NaN,
522+
fun: someFun,
523+
});
524+
});
525+
526+
test('by ignoring normalization depth', () => {
527+
const alreadyNormalizedObj = {
528+
three: {
529+
more: {
530+
layers: '!',
531+
},
532+
},
533+
};
534+
535+
addNonEnumerableProperty(alreadyNormalizedObj, '__sentry_skip_normalization__', true);
536+
537+
const obj = {
538+
foo: {
539+
bar: {
540+
baz: alreadyNormalizedObj,
541+
boo: {
542+
bam: {
543+
pow: 'poof',
544+
},
545+
},
546+
},
547+
},
548+
};
549+
550+
const result = normalize(obj, 4);
551+
552+
expect(result?.foo?.bar?.baz?.three?.more?.layers).toBe('!');
553+
expect(result?.foo?.bar?.boo?.bam?.pow).not.toBe('poof');
554+
});
555+
});
507556
});

0 commit comments

Comments
 (0)