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

findSchemaDefinition doesn't detect cyclic references #4116

Closed
3 of 4 tasks
MarttiR opened this issue Mar 6, 2024 · 7 comments · Fixed by #4123
Closed
3 of 4 tasks

findSchemaDefinition doesn't detect cyclic references #4116

MarttiR opened this issue Mar 6, 2024 · 7 comments · Fixed by #4123
Labels
bug help wanted utils Related to @rjsf/utils

Comments

@MarttiR
Copy link
Contributor

MarttiR commented Mar 6, 2024

Prerequisites

What theme are you using?

core

Version

5.17.1

Current Behavior

Consider a schema that (accidentally!) has a self-referencing definition like so:

{
  "schema": {
    "definitions": {
      "oops": {
        "$ref": "#/definitions/oops",
      },
    },
  },
}

During validation, findSchemaDefinition enters a recursive loop until the runtime aborts with Maximum call stack size exceeded (Chrome) or function nested too deeply (Firefox).

Expected Behavior

findSchemaDefinition could detect that the "resolved" $ref is in fact the same $ref and throw, alerting developers to the issue in their schema.

Steps To Reproduce

No response

Environment

No response

Anything else?

No response

@MarttiR MarttiR added bug needs triage Initial label given, to be assigned correct labels and assigned labels Mar 6, 2024
@heath-freenome
Copy link
Member

heath-freenome commented Mar 8, 2024

@MarttiR Good catch. We've dealt with cyclic $refs before by stopping once a recursion has been detected. There is many examples of this in the retrieveSchema() functionality. Are you willing to push a PR that does the following refactor with the necessary tests? NOTE: you may need to fix bugs here :)

// Modify existing docs, with addition of recurseList and description of how it is used
export function findSchemaDefinitionRecurse<S extends StrictRJSFSchema = RJSFSchema>(
  $ref?: string,
  rootSchema: S = {} as S,
  childSchema: S,
  recurseList,
): S {
  if (recurseList.includes($ref)) {
    return childSchema;
  }
  ... // All of the existing code except changing the recursion call to:
     const subSchema = findSchemaDefinitionRecurse<S>(theRef, rootSchema, current, [...recurseList, theRef]);
  ... // The remaining code
}
// Use existing docs
export default function findSchemaDefinition<S extends StrictRJSFSchema = RJSFSchema>(
  $ref?: string,
  rootSchema: S = {} as S
): S {
  const recurseList: string[] = [];
  return findSchemaDefinition($ref, rootSchema, rootSchema, recurseList);
}

@heath-freenome heath-freenome added help wanted utils Related to @rjsf/utils and removed needs triage Initial label given, to be assigned correct labels and assigned labels Mar 8, 2024
@MarttiR
Copy link
Contributor Author

MarttiR commented Mar 10, 2024

Sure, I can do it.

I think the appropriate fix is to throw an error when a cycle is detected, so that developers are alerted to the actual issue.

@heath-freenome
Copy link
Member

@MarttiR Cyclic dependencies like this with $refs are actually valid. So rather than throwing, you'll want to stop recursing

@MarttiR
Copy link
Contributor Author

MarttiR commented Mar 15, 2024

@heath-freenome Thanks! To me, it feels these definitions are degenerate, as they are self-referential. Could you give an example of a schema where such definitions would be valid, so that I can understand how to form the test cases?

Edit: nevermind, I see there are some example cases in testData. I will look into it.

@MarttiR
Copy link
Contributor Author

MarttiR commented Mar 17, 2024

@heath-freenome It looks like we are talking about two different cases.

It is indeed valid for a property schema to have child properties defined as a reference to itself.

For example, a menu structure is a classic example of that pattern:

- title: "First level menu item"
  children:
      - title: "Second level menu item"
      - title: "Another second level menu item"
        children:
            - title: "Third level menu item"

and so on.

For such a thing, it makes sense to have a schema like

{
  "definitions": {
    "MenuItem": {
      "type": "object",
      "properties": {
        "title": {
          "type": "string"
        },
        "children": {
          "$ref": "#/definitions/MenuItem"
        }
      }
    }
  }
}

However, the issue here is not with schema resolution, but definition resolution.

The problematic case is a definition property that refers to itself either directly, or indirectly through a chain of other definitions.

{
  "definitions": {
    "SelfReferential": {
      "$ref": "#/definitions/SelfReferential"
    }
  }
}

The crucial difference is that the references are not in child properties. Rather, the definition has become tautological, and is likely either a degenerate result of a bad schema transformation, or simply a user error.

In my mind, my current PR is a minimal and sufficient fix for the issue:

First, findSchemaDefinition never tries to recurse into the properties of a found definition, so it doesn't need the same recursion tests as retrieveSchema: it always returns the first level only, for any schema.

Secondly, resolveAllReferences of retrieveSchema already uses findSchemaDefinition for looking up references, so it should also be covered by the tests added here.

@heath-freenome
Copy link
Member

@MarttiR Ah... You raise a good point, so it's only the immediate self-recursion that you have to solve for? Or does the nested circular reference also pose the same issue?

@MarttiR
Copy link
Contributor Author

MarttiR commented Mar 24, 2024

@heath-freenome Only self-referential references (as in "ultimately ends up defining itself") are detected, as that's what causes the accidental baseless recursion here.

The "nested" case (as in "defines its children using its own definition") fixed earlier in retrieveSchema is never handled recursively by findSchemaDefinition – no actions needed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug help wanted utils Related to @rjsf/utils
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants