Skip to content

Commit e0c322a

Browse files
committed
Added tests
Also included support for the `traverse` function, which experienced the same issue.
1 parent f63a5a2 commit e0c322a

File tree

3 files changed

+59
-6
lines changed

3 files changed

+59
-6
lines changed

src/__tests__/ParseObject-test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ const ParseObject = require('../ParseObject').default;
153153
const ParseOp = require('../ParseOp');
154154
const RESTController = require('../RESTController');
155155
const SingleInstanceStateController = require('../SingleInstanceStateController');
156+
const encode = require('../encode').default;
156157
const unsavedChildren = require('../unsavedChildren').default;
157158

158159
const mockXHR = require('./test_helpers/mockXHR');
@@ -3855,4 +3856,41 @@ describe('ParseObject pin', () => {
38553856
});
38563857
CoreManager.set('ALLOW_CUSTOM_OBJECT_ID', false);
38573858
});
3859+
3860+
it('handles unsaved circular references', async () => {
3861+
const xhrs = [];
3862+
RESTController._setXHR(function () {
3863+
const xhr = {
3864+
setRequestHeader: jest.fn(),
3865+
open: jest.fn(),
3866+
send: jest.fn(),
3867+
status: 200,
3868+
readyState: 4,
3869+
};
3870+
xhrs.push(xhr);
3871+
return xhr;
3872+
});
3873+
3874+
const a = {};
3875+
const b = {};
3876+
a.b = b;
3877+
b.a = a;
3878+
3879+
const object = new ParseObject('Test');
3880+
object.set('a', a);
3881+
expect(() => {
3882+
object.save();
3883+
}).toThrowError(
3884+
'Maximum recursive calls exceeded in traverse function. Potential infinite recursion detected.'
3885+
);
3886+
});
3887+
3888+
it('throws error for infinite recursion', () => {
3889+
const circularObject = {};
3890+
circularObject.circularReference = circularObject;
3891+
3892+
expect(() => {
3893+
encode(circularObject, false, false, [], false);
3894+
}).toThrowError('Maximum recursive calls exceeded in encode function. Potential infinite recursion detected.');
3895+
});
38583896
});

src/encode.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ function encode(
2323
counter++;
2424

2525
if (counter > MAX_RECURSIVE_CALLS) {
26-
console.error('Maximum recursive calls exceeded in encode function. Potential infinite recursion detected.');
26+
const message = 'Maximum recursive calls exceeded in encode function. Potential infinite recursion detected.';
27+
console.error(message);
2728
console.error('Value causing potential infinite recursion:', value);
2829
console.error('Disallow objects:', disallowObjects);
2930
console.error('Force pointers:', forcePointers);
3031
console.error('Seen:', seen);
3132
console.error('Offline:', offline);
3233

33-
throw new Error('Maximum recursive calls exceeded in encode function. Potential infinite recursion detected.');
34+
throw new Error(message);
3435
}
3536

3637
if (value instanceof ParseObject) {

src/unsavedChildren.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import ParseFile from './ParseFile';
66
import ParseObject from './ParseObject';
77
import ParseRelation from './ParseRelation';
88

9+
const MAX_RECURSIVE_CALLS = 999;
10+
911
type EncounterMap = {
1012
objects: { [identifier: string]: ParseObject | boolean },
1113
files: Array<ParseFile>,
@@ -48,8 +50,20 @@ function traverse(
4850
obj: ParseObject,
4951
encountered: EncounterMap,
5052
shouldThrow: boolean,
51-
allowDeepUnsaved: boolean
53+
allowDeepUnsaved: boolean,
54+
counter: number = 0
5255
) {
56+
counter++;
57+
58+
if (counter > MAX_RECURSIVE_CALLS) {
59+
const message = 'Maximum recursive calls exceeded in traverse function. Potential infinite recursion detected.';
60+
console.error(message);
61+
console.error('Object causing potential infinite recursion:', obj);
62+
console.error('Encountered objects:', encountered);
63+
64+
throw new Error(message);
65+
}
66+
5367
if (obj instanceof ParseObject) {
5468
if (!obj.id && shouldThrow) {
5569
throw new Error('Cannot create a pointer to an unsaved Object.');
@@ -60,7 +74,7 @@ function traverse(
6074
const attributes = obj.attributes;
6175
for (const attr in attributes) {
6276
if (typeof attributes[attr] === 'object') {
63-
traverse(attributes[attr], encountered, !allowDeepUnsaved, allowDeepUnsaved);
77+
traverse(attributes[attr], encountered, !allowDeepUnsaved, allowDeepUnsaved, counter);
6478
}
6579
}
6680
}
@@ -78,13 +92,13 @@ function traverse(
7892
if (Array.isArray(obj)) {
7993
obj.forEach(el => {
8094
if (typeof el === 'object') {
81-
traverse(el, encountered, shouldThrow, allowDeepUnsaved);
95+
traverse(el, encountered, shouldThrow, allowDeepUnsaved, counter);
8296
}
8397
});
8498
}
8599
for (const k in obj) {
86100
if (typeof obj[k] === 'object') {
87-
traverse(obj[k], encountered, shouldThrow, allowDeepUnsaved);
101+
traverse(obj[k], encountered, shouldThrow, allowDeepUnsaved, counter);
88102
}
89103
}
90104
}

0 commit comments

Comments
 (0)