fix: exponential slowdown with cross-referencing oneOf/anyOf schemas#4991
Open
adamrimon wants to merge 4 commits intorjsf-team:mainfrom
Open
fix: exponential slowdown with cross-referencing oneOf/anyOf schemas#4991adamrimon wants to merge 4 commits intorjsf-team:mainfrom
oneOf/anyOf schemas#4991adamrimon wants to merge 4 commits intorjsf-team:mainfrom
Conversation
oneOf/anyOf schemas
f36f7ef to
c4d611f
Compare
…roperties before calling isValid
…ive getClosestMatchingOption call for nested oneOf
c4d611f to
8204727
Compare
Contributor
|
Here are a few thoughts:
|
Member
@x0k another fine analysis. Thanks for the input. And yes, the @adamrimon You are doing great work! Love the willingness to tackle some hard problems. Let me know if you need some support addressing the points raised above. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reasons for making this change
Fixes #4990
Schemas where definitions cross-reference each other through
oneOf/anyOfcan freeze the browser. This PR fixes two sources of exponential work.1. Shallow validation in
getFirstMatchingOption(getFirstMatchingOption.ts)getFirstMatchingOptioncallsvalidator.isValid()for each option. When properties containoneOf/anyOf/$ref, the validator recursively checks all nested branches, growing exponentially slower with depth.This function only needs to check structural shape (right property names and types), not which nested branch matches best. That's handled later by scoring.
Fixed by replacing properties containing
oneOf/anyOf/allOf/$refwith{}before validation. This can only widen matches, never miss the correct one.2. Bounded recursion in
calculateIndexScore(getClosestMatchingOption.ts)calculateIndexScorecalledgetClosestMatchingOptionfor nestedoneOf/anyOfproperties, which scores all N options at each level, resulting in O(N^D) work.Replaced with
getFirstMatchingOption(picks the first match, O(N)) followed by recursivecalculateIndexScoreon that single match. Complexity drops from O(N^D) to O(N² × D), bounded by form data depth.Also fixes a pre-existing bug where a $ref that resolved to a oneOf/anyOf would return early without evaluating the options. The $ref is now resolved inline and falls through to the oneOf/anyOf handling.
Trade-offs
Both fixes reduce matching precision in a narrow edge case: when two options are structurally identical (same property names, same top-level types) and differ only in nested
oneOf/anyOfcontent.In practice, the scoring phase still recurses into nested
oneOf/anyOf(only the validation ingetFirstMatchingOptionis shallow), so it resolves these ambiguities by checking types,const, anddefaultvalues at every level. Schemas that use adiscriminatorfield are unaffected entirely.Checklist
npx nx run-many --target=build --exclude=@rjsf/docs && npm run test:updateto update snapshots, if needed.