Skip to content
This repository was archived by the owner on Sep 14, 2020. It is now read-only.

Commit 6424d37

Browse files
bengourleykattrali
authored andcommitted
fix(typedMap): Support converting circular/nested structures
typedMap(obj) would exceed the max stack size if it was given a circular or deeply nested data structure. This change replaces circular references and levels of nesting above 10 with the strings "[circular]" and "[max depth exceeded]" respectively. Fixes #218
1 parent 6748b6d commit 6424d37

2 files changed

Lines changed: 146 additions & 2 deletions

File tree

src/Bugsnag.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,8 @@ const allowedMapObjectTypes = ['string', 'number', 'boolean'];
379379
* Convert an object into a structure with types suitable for serializing
380380
* across to native code.
381381
*/
382-
const typedMap = function(map) {
382+
const typedMap = function(map, maxDepth = 10, depth = 0, seen = new Set()) {
383+
seen.add(map)
383384
const output = {};
384385
for (const key in map) {
385386
if (!{}.hasOwnProperty.call(map, key)) continue;
@@ -390,7 +391,13 @@ const typedMap = function(map) {
390391
if (value == undefined || (typeof value === 'number' && isNaN(value))) {
391392
output[key] = {type: 'string', value: String(value)}
392393
} else if (typeof value === 'object') {
393-
output[key] = {type: 'map', value: typedMap(value)};
394+
if (seen.has(value)) {
395+
output[key] = {type: 'string', value: '[circular]'}
396+
} else if (depth === maxDepth) {
397+
output[key] = {type: 'string', value: '[max depth exceeded]'}
398+
} else {
399+
output[key] = {type: 'map', value: typedMap(value, maxDepth, depth + 1, seen)};
400+
}
394401
} else {
395402
const type = typeof value;
396403
if (allowedMapObjectTypes.includes(type)) {
@@ -402,3 +409,5 @@ const typedMap = function(map) {
402409
}
403410
return output;
404411
}
412+
413+
export { typedMap };

src/__tests__/Bugsnag.test.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,3 +356,138 @@ test('{enable|disable}ConsoleBreadCrumbs(): gracefully handles serialization edg
356356
// switched off for the next test ¯\_(ツ)_/¯
357357
c.disableConsoleBreadCrumbs()
358358
})
359+
360+
361+
test('typedMap converts arrays, objects, strings and numbers', () => {
362+
const { typedMap } = require('../Bugsnag')
363+
expect(typedMap({
364+
arr: [ 0, 1, 2 ],
365+
obj: {
366+
a: 'a',
367+
b: 'b',
368+
c: 'c'
369+
},
370+
x: 10,
371+
y: 'str'
372+
})).toEqual({
373+
arr: {
374+
type: 'map',
375+
value: {
376+
'0': { type: 'number', value: 0 },
377+
'1': { type: 'number', value: 1 },
378+
'2': { type: 'number', value: 2 }
379+
}
380+
},
381+
obj: {
382+
type: 'map',
383+
value: {
384+
a: { type: 'string', value: 'a' },
385+
b: { type: 'string', value: 'b' },
386+
c: { type: 'string', value: 'c' }
387+
}
388+
},
389+
x: { type: 'number', value: 10 },
390+
y: { type: 'string', value: 'str' }
391+
})
392+
})
393+
394+
test('typedMap handles circular structures', () => {
395+
const { typedMap } = require('../Bugsnag')
396+
const objA = {};
397+
objA.ref = objA;
398+
399+
// this tests the top level object being referred to as a node
400+
expect(typedMap(objA)).toEqual({ ref: { type: 'string', value: '[circular]' } })
401+
402+
const objB = {};
403+
objB.leafA = {};
404+
objB.leafB = {};
405+
objB.leafA.ref = objB.leafB
406+
objB.leafB.ref = objB.leafA
407+
408+
// this tests subtrees referring to eachother
409+
expect(typedMap(objB)).toEqual({
410+
leafA: {
411+
type: 'map',
412+
value: {
413+
ref: {
414+
type: 'map',
415+
value: {
416+
ref: { type: 'string', value: '[circular]' }
417+
}
418+
}
419+
}
420+
},
421+
leafB: { type: 'string', value: '[circular]' }
422+
})
423+
})
424+
425+
test('typedMap handles deep nesting', () => {
426+
const { typedMap } = require('../Bugsnag')
427+
const nesty = {
428+
zero: {
429+
one: {
430+
two: {
431+
three: {
432+
four: {
433+
five: { six: { seven: { eight: { nine: { ten: { eleven: 'TOO_DEEP!' } } } } } }
434+
}
435+
}
436+
}
437+
}
438+
}
439+
};
440+
expect(typedMap(nesty)).toEqual({
441+
zero: {
442+
type: 'map',
443+
value: {
444+
one: {
445+
type: 'map',
446+
value: {
447+
two: {
448+
type: 'map',
449+
value: {
450+
three: {
451+
type: 'map',
452+
value: {
453+
four: {
454+
type: 'map',
455+
value: {
456+
five: {
457+
type: 'map',
458+
value: {
459+
six: {
460+
type: 'map',
461+
value: {
462+
seven: {
463+
type: 'map',
464+
value: {
465+
eight: {
466+
type: 'map',
467+
value: {
468+
nine: {
469+
type: 'map',
470+
value: {
471+
ten: { type: 'string', value: '[max depth exceeded]' }
472+
}
473+
}
474+
}
475+
}
476+
}
477+
}
478+
}
479+
}
480+
}
481+
}
482+
}
483+
}
484+
}
485+
}
486+
}
487+
}
488+
}
489+
}
490+
}
491+
}
492+
})
493+
})

0 commit comments

Comments
 (0)