Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stop infinite recursion w/ crawling external refs #231

Merged
merged 2 commits into from
May 21, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {WeakMap} 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 WeakMap();
cody-greene marked this conversation as resolved.
Show resolved Hide resolved
let promises = [];

if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !seen.has(obj)) {
seen.set(obj, 1); // 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