diff --git a/src/FSharp.Data.Xml.Core/XsdInference.fs b/src/FSharp.Data.Xml.Core/XsdInference.fs index a25f6b787..6b3212374 100644 --- a/src/FSharp.Data.Xml.Core/XsdInference.fs +++ b/src/FSharp.Data.Xml.Core/XsdInference.fs @@ -104,9 +104,12 @@ module XsdParsing = let elements = System.Collections.Generic.Dictionary() + let visitingGroups = System.Collections.Generic.HashSet() + member x.GetElement name = getElm name member x.GetSubstitutions elm = getSubst elm member x.Elements = elements + member x.VisitingGroups = visitingGroups open XsdModel @@ -183,7 +186,17 @@ module XsdParsing = match par with | :? XmlSchemaAny -> Any occurs | :? XmlSchemaGroupBase as grp -> parseParticles grp - | :? XmlSchemaGroupRef as grpRef -> parseParticle ctx grpRef.Particle + | :? XmlSchemaGroupRef as grpRef -> + // Guard against circular group references (e.g. group A → group B → group A). + // XSD allows groups to reference each other transitively; without a cycle guard + // this causes unbounded recursion and a StackOverflowException. + if ctx.VisitingGroups.Contains grpRef.RefName then + Empty // break the cycle + else + ctx.VisitingGroups.Add grpRef.RefName |> ignore + let result = parseParticle ctx grpRef.Particle + ctx.VisitingGroups.Remove grpRef.RefName |> ignore + result | :? XmlSchemaElement as elm -> let e = if elm.RefName.IsEmpty then diff --git a/tests/FSharp.Data.DesignTime.Tests/InferenceTests.fs b/tests/FSharp.Data.DesignTime.Tests/InferenceTests.fs index 560e41d32..414c82ec3 100644 --- a/tests/FSharp.Data.DesignTime.Tests/InferenceTests.fs +++ b/tests/FSharp.Data.DesignTime.Tests/InferenceTests.fs @@ -934,3 +934,35 @@ let ``abstract elements can be recursive``() = getInferedTypes formulaXsd [| sample1; sample2 |] |> ignore +[] +let ``circular group references do not cause a stack overflow``() = + + // Schema with a group that (indirectly) creates a recursive content model: + // RecursiveGroup contains element "children" whose anonymous type references RecursiveGroup. + // Without a GroupRef cycle guard, this can cause unbounded recursion in parseParticle + // on .NET runtimes where the compiled XmlSchema object graph contains shared group particle references. + let xsd = + """ + + + + + + + + + + + + + + + + + + """ + + // Must complete without StackOverflowException + getInferedTypeFromSchema xsd |> ignore +