Skip to content

Commit 8b70f18

Browse files
author
Phil Sturgeon
authored
Merge pull request #231 from cody-greene/infinite-recursion
stop infinite recursion w/ crawling external refs
2 parents 3363715 + efbb726 commit 8b70f18

File tree

2 files changed

+61
-3
lines changed

2 files changed

+61
-3
lines changed

lib/resolve-external.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,20 @@ function resolveExternal (parser, options) {
4444
* @param {string} path - The full path of `obj`, possibly with a JSON Pointer in the hash
4545
* @param {$Refs} $refs
4646
* @param {$RefParserOptions} options
47+
* @param {Set} seen - Internal.
4748
*
4849
* @returns {Promise[]}
4950
* Returns an array of promises. There will be one promise for each JSON reference in `obj`.
5051
* If `obj` does not contain any JSON references, then the array will be empty.
5152
* If any of the JSON references point to files that contain additional JSON references,
5253
* then the corresponding promise will internally reference an array of promises.
5354
*/
54-
function crawl (obj, path, $refs, options) {
55+
function crawl (obj, path, $refs, options, seen) {
56+
seen = seen || new Set();
5557
let promises = [];
5658

57-
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
59+
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !seen.has(obj)) {
60+
seen.add(obj); // Track previously seen objects to avoid infinite recursion
5861
if ($Ref.isExternal$Ref(obj)) {
5962
promises.push(resolve$Ref(obj, path, $refs, options));
6063
}
@@ -67,7 +70,7 @@ function crawl (obj, path, $refs, options) {
6770
promises.push(resolve$Ref(value, keyPath, $refs, options));
6871
}
6972
else {
70-
promises = promises.concat(crawl(value, keyPath, $refs, options));
73+
promises = promises.concat(crawl(value, keyPath, $refs, options, seen));
7174
}
7275
}
7376
}

test/specs/circular/circular.spec.js

+55
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ describe("Schema with circular (recursive) $refs", () => {
3636
expect(schema.definitions.child.properties.pet).to.equal(schema.definitions.pet);
3737
});
3838

39+
it("should double dereference successfully", async () => {
40+
const firstPassSchema = await $RefParser.dereference(path.rel("specs/circular/circular-self.yaml"));
41+
let parser = new $RefParser();
42+
const schema = await parser.dereference(firstPassSchema);
43+
expect(schema).to.equal(parser.schema);
44+
expect(schema).to.deep.equal(dereferencedSchema.self);
45+
// The "circular" flag should be set
46+
expect(parser.$refs.circular).to.equal(true);
47+
// Reference equality
48+
expect(schema.definitions.child.properties.pet).to.equal(schema.definitions.pet);
49+
});
50+
3951
it('should produce the same results if "options.$refs.circular" is "ignore"', async () => {
4052
let parser = new $RefParser();
4153
const schema = await parser.dereference(path.rel("specs/circular/circular-self.yaml"), { dereference: { circular: "ignore" }});
@@ -103,6 +115,19 @@ describe("Schema with circular (recursive) $refs", () => {
103115
expect(schema.definitions.person.properties.pet).to.equal(schema.definitions.pet);
104116
});
105117

118+
it("should double dereference successfully", async () => {
119+
let parser = new $RefParser();
120+
const firstPassSchema = await $RefParser.dereference(path.rel("specs/circular/circular-ancestor.yaml"));
121+
const schema = await parser.dereference(firstPassSchema);
122+
expect(schema).to.equal(parser.schema);
123+
expect(schema).to.deep.equal(dereferencedSchema.ancestor.fullyDereferenced);
124+
// The "circular" flag should be set
125+
expect(parser.$refs.circular).to.equal(true);
126+
// Reference equality
127+
expect(schema.definitions.person.properties.spouse).to.equal(schema.definitions.person);
128+
expect(schema.definitions.person.properties.pet).to.equal(schema.definitions.pet);
129+
});
130+
106131
it('should not dereference circular $refs if "options.$refs.circular" is "ignore"', async () => {
107132
let parser = new $RefParser();
108133
const schema = await parser.dereference(path.rel("specs/circular/circular-ancestor.yaml"), { dereference: { circular: "ignore" }});
@@ -174,6 +199,21 @@ describe("Schema with circular (recursive) $refs", () => {
174199
.to.equal(schema.definitions.parent);
175200
});
176201

202+
it("should double dereference successfully", async () => {
203+
let parser = new $RefParser();
204+
const firstPassSchema = await $RefParser.dereference(path.rel("specs/circular/circular-indirect.yaml"));
205+
const schema = await parser.dereference(firstPassSchema);
206+
expect(schema).to.equal(parser.schema);
207+
expect(schema).to.deep.equal(dereferencedSchema.indirect.fullyDereferenced);
208+
// The "circular" flag should be set
209+
expect(parser.$refs.circular).to.equal(true);
210+
// Reference equality
211+
expect(schema.definitions.parent.properties.children.items)
212+
.to.equal(schema.definitions.child);
213+
expect(schema.definitions.child.properties.parents.items)
214+
.to.equal(schema.definitions.parent);
215+
});
216+
177217
it('should not dereference circular $refs if "options.$refs.circular" is "ignore"', async () => {
178218
let parser = new $RefParser();
179219
const schema = await parser.dereference(path.rel("specs/circular/circular-indirect.yaml"), { dereference: { circular: "ignore" }});
@@ -245,6 +285,21 @@ describe("Schema with circular (recursive) $refs", () => {
245285
.to.equal(schema.definitions.child);
246286
});
247287

288+
it("should double dereference successfully", async () => {
289+
let parser = new $RefParser();
290+
const firstPassSchema = await parser.dereference(path.rel("specs/circular/circular-indirect-ancestor.yaml"));
291+
const schema = await parser.dereference(firstPassSchema);
292+
expect(schema).to.equal(parser.schema);
293+
expect(schema).to.deep.equal(dereferencedSchema.indirectAncestor.fullyDereferenced);
294+
// The "circular" flag should be set
295+
expect(parser.$refs.circular).to.equal(true);
296+
// Reference equality
297+
expect(schema.definitions.parent.properties.child)
298+
.to.equal(schema.definitions.child);
299+
expect(schema.definitions.child.properties.children.items)
300+
.to.equal(schema.definitions.child);
301+
});
302+
248303
it('should not dereference circular $refs if "options.$refs.circular" is "ignore"', async () => {
249304
let parser = new $RefParser();
250305
const schema = await parser.dereference(path.rel("specs/circular/circular-indirect-ancestor.yaml"), { dereference: { circular: "ignore" }});

0 commit comments

Comments
 (0)