Skip to content

Commit b81488f

Browse files
authored
Handle circular references in custom data (#990)
* fix: handle circular references in custom data * test: use deepStrictEqual to compare objects for equality
1 parent 0aab52d commit b81488f

File tree

3 files changed

+95
-10
lines changed

3 files changed

+95
-10
lines changed

src/utility.js

+37-4
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,37 @@ function wrapCallback(logger, f) {
398398
};
399399
}
400400

401+
function nonCircularClone(obj) {
402+
var seen = [obj];
403+
404+
function clone(obj, seen) {
405+
var value, name, newSeen, result = {};
406+
407+
try {
408+
for (name in obj) {
409+
value = obj[name];
410+
411+
if (value && (isType(value, 'object') || isType(value, 'array'))) {
412+
if (seen.includes(value)) {
413+
result[name] = 'Removed circular reference: ' + typeName(value);
414+
} else {
415+
newSeen = seen.slice();
416+
newSeen.push(value);
417+
result[name] = clone(value, newSeen);
418+
}
419+
continue;
420+
}
421+
422+
result[name] = value;
423+
}
424+
} catch (e) {
425+
result = 'Failed cloning custom data: ' + e.message;
426+
}
427+
return result;
428+
}
429+
return clone(obj, seen);
430+
}
431+
401432
function createItem(args, logger, notifier, requestKeys, lambdaContext) {
402433
var message, err, custom, callback, request;
403434
var arg;
@@ -455,10 +486,12 @@ function createItem(args, logger, notifier, requestKeys, lambdaContext) {
455486
}
456487
}
457488

489+
// if custom is an array this turns it into an object with integer keys
490+
if (custom) custom = nonCircularClone(custom);
491+
458492
if (extraArgs.length > 0) {
459-
// if custom is an array this turns it into an object with integer keys
460-
custom = merge(custom);
461-
custom.extraArgs = extraArgs;
493+
if (!custom) custom = nonCircularClone({});
494+
custom.extraArgs = nonCircularClone(extraArgs);
462495
}
463496

464497
var item = {
@@ -503,7 +536,7 @@ function addErrorContext(item, errors) {
503536
try {
504537
for (var i = 0; i < errors.length; ++i) {
505538
if (errors[i].hasOwnProperty('rollbarContext')) {
506-
custom = merge(custom, errors[i].rollbarContext);
539+
custom = merge(custom, nonCircularClone(errors[i].rollbarContext));
507540
contextAdded = true;
508541
}
509542
}

test/browser.core.test.js

+52
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,58 @@ describe('log', function() {
503503
done();
504504
})
505505

506+
it('should remove circular references in custom data', function(done) {
507+
var server = window.server;
508+
stubResponse(server);
509+
server.requests.length = 0;
510+
511+
var options = {
512+
accessToken: 'POST_CLIENT_ITEM_TOKEN',
513+
addErrorContext: true
514+
};
515+
var rollbar = window.rollbar = new Rollbar(options);
516+
517+
var err = new Error('test error');
518+
var contextData = { extra: 'baz' }
519+
contextData.data = contextData;
520+
var context = { err: 'test', contextData: contextData };
521+
err.rollbarContext = context;
522+
523+
var array = ['one', 'two'];
524+
array.push(array);
525+
var custom = { foo: 'bar', array: array };
526+
var notCircular = { key: 'value' };
527+
custom.notCircular1 = notCircular;
528+
custom.notCircular2 = notCircular;
529+
custom.self = custom;
530+
rollbar.error(err, custom);
531+
532+
server.respond();
533+
534+
var body = JSON.parse(server.requests[0].requestBody);
535+
536+
expect(body.data.body.trace.exception.message).to.eql('test error');
537+
expect(body.data.custom.foo).to.eql('bar');
538+
expect(body.data.custom.err).to.eql('test');
539+
540+
// Duplicate objects are allowed when there is no circular reference.
541+
expect(body.data.custom.notCircular1).to.eql(notCircular);
542+
expect(body.data.custom.notCircular2).to.eql(notCircular);
543+
544+
expect(body.data.custom.self).to.eql(
545+
'Removed circular reference: object'
546+
);
547+
expect(body.data.custom.array).to.eql([
548+
'one', 'two', 'Removed circular reference: array'
549+
]);
550+
expect(body.data.custom.contextData).to.eql({
551+
extra: 'baz',
552+
data: 'Removed circular reference: object'
553+
});
554+
555+
done();
556+
})
557+
506558
it('should send message when called with only null arguments', function(done) {
507559
var server = window.server;
508560
stubResponse(server);

test/server.rollbar.test.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ vows.describe('rollbar')
278278
var item = r.client.logCalls[r.client.logCalls.length - 1].item;
279279
assert.equal(item.message, message);
280280
assert.equal(item.request, request);
281-
assert.equal(item.custom, custom);
281+
assert.deepStrictEqual(item.custom, custom);
282282
item.callback();
283283
assert.isTrue(callbackCalled);
284284
}
@@ -332,7 +332,7 @@ vows.describe('rollbar')
332332
var item = r.client.logCalls[r.client.logCalls.length - 1].item
333333
assert.equal(item.message, message)
334334
assert.equal(item.request, request)
335-
assert.equal(item.custom, custom)
335+
assert.deepStrictEqual(item.custom, custom)
336336
},
337337
'should work with request and custom and callback': function(r) {
338338
var message = 'hello'
@@ -346,7 +346,7 @@ vows.describe('rollbar')
346346
var item = r.client.logCalls[r.client.logCalls.length - 1].item
347347
assert.equal(item.message, message)
348348
assert.equal(item.request, request)
349-
assert.equal(item.custom, custom)
349+
assert.deepStrictEqual(item.custom, custom)
350350
item.callback();
351351
assert.isTrue(callbackCalled);
352352
}
@@ -399,7 +399,7 @@ vows.describe('rollbar')
399399
var item = r.client.logCalls[r.client.logCalls.length - 1].item
400400
assert.equal(item.err, err)
401401
assert.equal(item.request, request)
402-
assert.equal(item.custom, custom)
402+
assert.deepStrictEqual(item.custom, custom)
403403
item.callback();
404404
assert.isTrue(callbackCalled);
405405
}
@@ -453,7 +453,7 @@ vows.describe('rollbar')
453453
var item = r.client.logCalls[r.client.logCalls.length - 1].item
454454
assert.equal(item.err, err)
455455
assert.equal(item.request, request)
456-
assert.equal(item.custom, custom)
456+
assert.deepStrictEqual(item.custom, custom)
457457
},
458458
'should work with request and custom and callback': function(r) {
459459
var err = new Error('hello!')
@@ -467,7 +467,7 @@ vows.describe('rollbar')
467467
var item = r.client.logCalls[r.client.logCalls.length - 1].item
468468
assert.equal(item.err, err)
469469
assert.equal(item.request, request)
470-
assert.equal(item.custom, custom)
470+
assert.deepStrictEqual(item.custom, custom)
471471
item.callback();
472472
assert.isTrue(callbackCalled);
473473
}

0 commit comments

Comments
 (0)