Skip to content

Commit

Permalink
Merge pull request #231 from cody-greene/infinite-recursion
Browse files Browse the repository at this point in the history
stop infinite recursion w/ crawling external refs
  • Loading branch information
Phil Sturgeon authored May 21, 2021
2 parents 3363715 + efbb726 commit 8b70f18
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 3 deletions.
9 changes: 6 additions & 3 deletions lib/resolve-external.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,20 @@ function resolveExternal (parser, options) {
* @param {string} path - The full path of `obj`, possibly with a JSON Pointer in the hash
* @param {$Refs} $refs
* @param {$RefParserOptions} options
* @param {Set} seen - Internal.
*
* @returns {Promise[]}
* Returns an array of promises. There will be one promise for each JSON reference in `obj`.
* If `obj` does not contain any JSON references, then the array will be empty.
* If any of the JSON references point to files that contain additional JSON references,
* then the corresponding promise will internally reference an array of promises.
*/
function crawl (obj, path, $refs, options) {
function crawl (obj, path, $refs, options, seen) {
seen = seen || new Set();
let promises = [];

if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !seen.has(obj)) {
seen.add(obj); // Track previously seen objects to avoid infinite recursion
if ($Ref.isExternal$Ref(obj)) {
promises.push(resolve$Ref(obj, path, $refs, options));
}
Expand All @@ -67,7 +70,7 @@ function crawl (obj, path, $refs, options) {
promises.push(resolve$Ref(value, keyPath, $refs, options));
}
else {
promises = promises.concat(crawl(value, keyPath, $refs, options));
promises = promises.concat(crawl(value, keyPath, $refs, options, seen));
}
}
}
Expand Down
55 changes: 55 additions & 0 deletions test/specs/circular/circular.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ describe("Schema with circular (recursive) $refs", () => {
expect(schema.definitions.child.properties.pet).to.equal(schema.definitions.pet);
});

it("should double dereference successfully", async () => {
const firstPassSchema = await $RefParser.dereference(path.rel("specs/circular/circular-self.yaml"));
let parser = new $RefParser();
const schema = await parser.dereference(firstPassSchema);
expect(schema).to.equal(parser.schema);
expect(schema).to.deep.equal(dereferencedSchema.self);
// The "circular" flag should be set
expect(parser.$refs.circular).to.equal(true);
// Reference equality
expect(schema.definitions.child.properties.pet).to.equal(schema.definitions.pet);
});

it('should produce the same results if "options.$refs.circular" is "ignore"', async () => {
let parser = new $RefParser();
const schema = await parser.dereference(path.rel("specs/circular/circular-self.yaml"), { dereference: { circular: "ignore" }});
Expand Down Expand Up @@ -103,6 +115,19 @@ describe("Schema with circular (recursive) $refs", () => {
expect(schema.definitions.person.properties.pet).to.equal(schema.definitions.pet);
});

it("should double dereference successfully", async () => {
let parser = new $RefParser();
const firstPassSchema = await $RefParser.dereference(path.rel("specs/circular/circular-ancestor.yaml"));
const schema = await parser.dereference(firstPassSchema);
expect(schema).to.equal(parser.schema);
expect(schema).to.deep.equal(dereferencedSchema.ancestor.fullyDereferenced);
// The "circular" flag should be set
expect(parser.$refs.circular).to.equal(true);
// Reference equality
expect(schema.definitions.person.properties.spouse).to.equal(schema.definitions.person);
expect(schema.definitions.person.properties.pet).to.equal(schema.definitions.pet);
});

it('should not dereference circular $refs if "options.$refs.circular" is "ignore"', async () => {
let parser = new $RefParser();
const schema = await parser.dereference(path.rel("specs/circular/circular-ancestor.yaml"), { dereference: { circular: "ignore" }});
Expand Down Expand Up @@ -174,6 +199,21 @@ describe("Schema with circular (recursive) $refs", () => {
.to.equal(schema.definitions.parent);
});

it("should double dereference successfully", async () => {
let parser = new $RefParser();
const firstPassSchema = await $RefParser.dereference(path.rel("specs/circular/circular-indirect.yaml"));
const schema = await parser.dereference(firstPassSchema);
expect(schema).to.equal(parser.schema);
expect(schema).to.deep.equal(dereferencedSchema.indirect.fullyDereferenced);
// The "circular" flag should be set
expect(parser.$refs.circular).to.equal(true);
// Reference equality
expect(schema.definitions.parent.properties.children.items)
.to.equal(schema.definitions.child);
expect(schema.definitions.child.properties.parents.items)
.to.equal(schema.definitions.parent);
});

it('should not dereference circular $refs if "options.$refs.circular" is "ignore"', async () => {
let parser = new $RefParser();
const schema = await parser.dereference(path.rel("specs/circular/circular-indirect.yaml"), { dereference: { circular: "ignore" }});
Expand Down Expand Up @@ -245,6 +285,21 @@ describe("Schema with circular (recursive) $refs", () => {
.to.equal(schema.definitions.child);
});

it("should double dereference successfully", async () => {
let parser = new $RefParser();
const firstPassSchema = await parser.dereference(path.rel("specs/circular/circular-indirect-ancestor.yaml"));
const schema = await parser.dereference(firstPassSchema);
expect(schema).to.equal(parser.schema);
expect(schema).to.deep.equal(dereferencedSchema.indirectAncestor.fullyDereferenced);
// The "circular" flag should be set
expect(parser.$refs.circular).to.equal(true);
// Reference equality
expect(schema.definitions.parent.properties.child)
.to.equal(schema.definitions.child);
expect(schema.definitions.child.properties.children.items)
.to.equal(schema.definitions.child);
});

it('should not dereference circular $refs if "options.$refs.circular" is "ignore"', async () => {
let parser = new $RefParser();
const schema = await parser.dereference(path.rel("specs/circular/circular-indirect-ancestor.yaml"), { dereference: { circular: "ignore" }});
Expand Down

0 comments on commit 8b70f18

Please sign in to comment.