Skip to content

Commit e32127a

Browse files
github-actions[bot]Repo AssistCopilotdsyme
authored
[Repo Assist] Fix #1419: Add cycle guard for XSD group references to prevent StackOverflow (#1626)
* Update test/build dependencies: FAKE 6.1.4, NUnit 3.13.3, FsUnit 4.2.0, FsCheck 2.16.6 - FAKE packages: 6.1.3 → 6.1.4 (patch) - NUnit: 3.13.1 → 3.13.3 (patch) - FsUnit: 4.0.4 → 4.2.0 (minor) - FsCheck: 2.15.1 → 2.16.6 (minor) Build: passes (0 errors) Tests: all offline tests pass; network tests skip due to sandbox Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix #1419: Add cycle guard for XSD group references in XsdInference Add a VisitingGroups HashSet to ParsingContext and guard the GroupRef case in parseParticle. When a group is already being visited, return Empty (an empty particle) instead of recursing infinitely. This prevents a StackOverflowException when parsing XSD schemas that contain groups forming cycles (e.g. group A contains group ref B, and group B's resolved content eventually leads back to group A via the compiled XmlSchema object graph). Add a test verifying that a recursive content model schema with a group referencing itself through element content completes without error. Closes #1419 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Repo Assist <repo-assist@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Don Syme <dsyme@users.noreply.github.com> Co-authored-by: Don Syme <dsyme@github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent e02503d commit e32127a

2 files changed

Lines changed: 46 additions & 1 deletion

File tree

src/FSharp.Data.Xml.Core/XsdInference.fs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,12 @@ module XsdParsing =
104104
let elements =
105105
System.Collections.Generic.Dictionary<XmlSchemaElement, XsdModel.XsdElement>()
106106

107+
let visitingGroups = System.Collections.Generic.HashSet<XmlQualifiedName>()
108+
107109
member x.GetElement name = getElm name
108110
member x.GetSubstitutions elm = getSubst elm
109111
member x.Elements = elements
112+
member x.VisitingGroups = visitingGroups
110113

111114

112115
open XsdModel
@@ -183,7 +186,17 @@ module XsdParsing =
183186
match par with
184187
| :? XmlSchemaAny -> Any occurs
185188
| :? XmlSchemaGroupBase as grp -> parseParticles grp
186-
| :? XmlSchemaGroupRef as grpRef -> parseParticle ctx grpRef.Particle
189+
| :? XmlSchemaGroupRef as grpRef ->
190+
// Guard against circular group references (e.g. group A → group B → group A).
191+
// XSD allows groups to reference each other transitively; without a cycle guard
192+
// this causes unbounded recursion and a StackOverflowException.
193+
if ctx.VisitingGroups.Contains grpRef.RefName then
194+
Empty // break the cycle
195+
else
196+
ctx.VisitingGroups.Add grpRef.RefName |> ignore
197+
let result = parseParticle ctx grpRef.Particle
198+
ctx.VisitingGroups.Remove grpRef.RefName |> ignore
199+
result
187200
| :? XmlSchemaElement as elm ->
188201
let e =
189202
if elm.RefName.IsEmpty then

tests/FSharp.Data.DesignTime.Tests/InferenceTests.fs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,3 +934,35 @@ let ``abstract elements can be recursive``() =
934934
getInferedTypes formulaXsd [| sample1; sample2 |]
935935
|> ignore
936936

937+
[<Test>]
938+
let ``circular group references do not cause a stack overflow``() =
939+
940+
// Schema with a group that (indirectly) creates a recursive content model:
941+
// RecursiveGroup contains element "children" whose anonymous type references RecursiveGroup.
942+
// Without a GroupRef cycle guard, this can cause unbounded recursion in parseParticle
943+
// on .NET runtimes where the compiled XmlSchema object graph contains shared group particle references.
944+
let xsd =
945+
"""
946+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
947+
elementFormDefault="qualified">
948+
<xs:group name="RecursiveGroup">
949+
<xs:sequence>
950+
<xs:element name="value" type="xs:string" minOccurs="0"/>
951+
<xs:element name="children" minOccurs="0">
952+
<xs:complexType>
953+
<xs:group ref="RecursiveGroup" minOccurs="0" maxOccurs="unbounded"/>
954+
</xs:complexType>
955+
</xs:element>
956+
</xs:sequence>
957+
</xs:group>
958+
<xs:element name="Root">
959+
<xs:complexType>
960+
<xs:group ref="RecursiveGroup"/>
961+
</xs:complexType>
962+
</xs:element>
963+
</xs:schema>
964+
"""
965+
966+
// Must complete without StackOverflowException
967+
getInferedTypeFromSchema xsd |> ignore
968+

0 commit comments

Comments
 (0)