| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Two defects compounded into a stack overflow in 'oasdiff flatten' on allOf: [$ref Node, <overlay referencing Node>] where Node is recursive: 1. MergeSpec's write-back (*s.Value = *m) copies the merged contents into the shared original object, but the subtree's self-references still point at the object Merge returned. A recursive schema's one-object cycle becomes a two-object cycle, so later attachment points see two distinct originals for one schema and the merge cache cannot unify them. redirectSchemaRefs now repairs the references after the copy, type-driven so new subschema fields are covered without being listed. 2. Merging sibling subschemas that dedup to a single schema flattened them anyway, and the in-flight cycle guard then pointed the fresh ref-less result at an ancestor: a cycle with no $ref, which the marshaler follows until the stack overflows. mergeSubschemas now reuses the lone schema, $ref intact, at every subschema merge site (items, contains, propertyNames, additionalProperties, properties). The merged output for the reproducer now inlines the combined shape once with its recursion expressed as $ref Node, so it serializes and the diff's ref-based circular guard sees the cycle. The #890 pin moves with the second change: merging a set that dedups to one schema yields that schema itself, cycle intact, rather than re-anchoring the cycle onto the merged root. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDvUFsg5nu1NBWQffErGDw
Codecov Report❌ Patch coverage is 97.67442% with 1 line in your changes missing coverage. Please review.
@@ Coverage Diff @@
## main #1233 +/- ##
==========================================
- Coverage 92.27% 92.26% -0.02%
==========================================
Files 347 347
Lines 14312 14306 -6
==========================================
- Hits 13207 13200 -7
- Misses 1105 1106 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness.
|
Sorry, something went wrong.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDvUFsg5nu1NBWQffErGDw
| Back | FazBrowse Home | New Git URL |
Repro
crashed with fatal error: stack overflow (found while verifying #1231, which fixed the same fixture's crash on the diff path; the flatten path crashes in SchemaRef.MarshalJSON instead).
Two compounding defects
1. MergeSpec's write-back breaks self-referential identity. *s.Value = *m copies the merged contents into the shared original object so every $ref sees the merge, but the merged subtree's self-references still point at the object Merge returned. A recursive schema's one-object cycle becomes a two-object cycle. Later attachment points then meet two distinct "originals" for one schema, and the per-attachment merge cache cannot unify them (confirmed by pointer tracing: two $ref: Node sites arrived at the items merge with different values). Fixed by redirectSchemaRefs: after the copy, walk the subtree and point references at the written-back object. The walk is reflection-driven, so a new subschema-bearing field is covered without being listed.
2. A subschema set that dedups to one schema was flattened anyway. resolveItems and friends only short-circuited on len == 1 by pointer, so two $ref: Node siblings (same value, distinct SchemaRef wrappers) went into flattenSchemas, whose in-flight cycle guard pointed the fresh ref-less result at an ancestor: a cycle with no $ref, which the marshaler follows until the stack overflows. The new mergeSubschemas dedups by value first and reuses the lone schema, $ref intact, at every subschema merge site (items, contains, propertyNames, additionalProperties, properties — the last had no single-schema shortcut at all).
Result
The merged reproducer now inlines the combined shape once, with its recursion expressed as $ref: Node — serializable, and visible to the diff's ref-based circular guard, so breaking --flatten-allof on this fixture no longer overflows even without #1231's in-flight guard.
#1231 remains necessary alongside this: an allOf over two distinct recursive components still merges to a ref-less cycle (the combined node has no component name a $ref could use), and only the in-flight diff guard keeps breaking --flatten-allof alive on that shape. Serializing that case in oasdiff flatten needs a synthetic hoisted component and is left to a follow-up issue.
One pinned identity moves deliberately: the #890 test asserted that merging allOf: [node, node] re-anchors the cycle onto the merged root. Merging a set that dedups to one schema now yields that schema itself, cycle intact (X ∧ X = X); the test asserts the preserved cycle instead.
Regression test: Test_MergeSpec_RecursiveOverlaySerializes (merged shape, preserved $ref, and a successful marshal of the whole spec). Full suite and lint pass.