This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch coheigea/walker-depth in repository https://gitbox.apache.org/repos/asf/ws-xmlschema.git
commit 8598c540cbbefe5e5df39798014764418f20086b Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Thu Sep 24 16:45:35 2026 +0100 Count a cached base type's whole derivation chain against the walk depth --- .../ws/commons/schema/walker/XmlSchemaScope.java | 42 +++++++++++++++------- .../schema/walker/WalkerDepthLimitTest.java | 35 ++++++++++++++++++ 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/walker/XmlSchemaScope.java b/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/walker/XmlSchemaScope.java index fb835948..f7cb595f 100644 --- a/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/walker/XmlSchemaScope.java +++ b/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/walker/XmlSchemaScope.java @@ -80,6 +80,13 @@ final class XmlSchemaScope { private int maxDepth; private final Set<QName> attributeGroupsInProgress = new HashSet<QName>(); + /* + * The number of types in this scope's derivation chain, counting its own: one more than the + * deepest scope it derives from. A cached scope is not walked again, so the recursion bound + * in walkWithCycleCheck() never sees the chain below it; this carries that chain's length. + */ + private int derivationDepth = 1; + /** * Initialization of members to be filled in during the walk. */ @@ -128,11 +135,7 @@ final class XmlSchemaScope { // Each level of derivation recurses; an acyclic chain can still // exhaust the thread stack. if (typesInProgress.size() >= maxDepth) { - throw new XmlSchemaException("The type " + getName(type, "{Anonymous}") - + " is derived through more than " + maxDepth - + " levels of base types; refusing to walk it. The limit" - + " may be changed with the " - + XmlSchemaWalker.MAX_DEPTH_PROPERTY + " system property."); + throw derivedTooDeeply(type); } if (!typesInProgress.add(type)) { throw new XmlSchemaException("Cyclic type derivation detected involving type " @@ -794,15 +797,28 @@ final class XmlSchemaScope { } private XmlSchemaScope getScope(XmlSchemaType type) { - if ((type.getQName() != null) && scopeCache.containsKey(type.getQName())) { - return scopeCache.get(type.getQName()); - } else { - XmlSchemaScope scope = new XmlSchemaScope(this, type); - if (type.getQName() != null) { - scopeCache.put(type.getQName(), scope); - } - return scope; + final boolean cached = (type.getQName() != null) && scopeCache.containsKey(type.getQName()); + final XmlSchemaScope scope = cached ? scopeCache.get(type.getQName()) : new XmlSchemaScope(this, type); + + // The whole chain counts, including the part a cached scope already walked: the types in + // progress above this point, then the base scope's own chain. + if (typesInProgress.size() + scope.derivationDepth > maxDepth) { + throw derivedTooDeeply(type); } + derivationDepth = Math.max(derivationDepth, scope.derivationDepth + 1); + + if (!cached && (type.getQName() != null)) { + scopeCache.put(type.getQName(), scope); + } + return scope; + } + + private XmlSchemaException derivedTooDeeply(XmlSchemaType type) { + return new XmlSchemaException("The type " + getName(type, "{Anonymous}") + + " is derived through more than " + maxDepth + + " levels of base types; refusing to walk it. The limit" + + " may be changed with the " + + XmlSchemaWalker.MAX_DEPTH_PROPERTY + " system property."); } private QName getUserRecognizedType(QName simpleType, XmlSchemaTypeInfo parent) { diff --git a/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/walker/WalkerDepthLimitTest.java b/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/walker/WalkerDepthLimitTest.java index f4cd8570..b852ad37 100644 --- a/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/walker/WalkerDepthLimitTest.java +++ b/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/walker/WalkerDepthLimitTest.java @@ -131,6 +131,41 @@ public class WalkerDepthLimitTest extends Assert { return body.toString(); } + /** + * S1 restricts S0, S2 restricts S1, and so on, and root has an optional child of type S_k for + * every k that is a multiple of step, in increasing order. Each child's type is walked with the + * types before it already walked and cached. + */ + private static String steppedRestrictionChain(int length, int step) { + StringBuilder body = new StringBuilder("<xs:element name=\"root\"><xs:complexType><xs:sequence>"); + for (int k = step; k < length; k += step) { + body.append("<xs:element name=\"c").append(k).append("\" type=\"tns:S").append(k) + .append("\" minOccurs=\"0\"/>"); + } + body.append("</xs:sequence></xs:complexType></xs:element>") + .append("<xs:simpleType name=\"S0\"><xs:restriction base=\"xs:string\"/></xs:simpleType>"); + for (int i = 1; i < length; i++) { + body.append("<xs:simpleType name=\"S").append(i).append("\"><xs:restriction base=\"tns:S") + .append(i - 1).append("\"/></xs:simpleType>"); + } + return body.toString(); + } + + /** + * A chain walked a step at a time used to pass: each step recursed only as far as the cached + * type before it, so the limit never saw the whole chain, and the type information it built + * was as deep as the chain. + */ + @Test + public void testDerivationChainWalkedInStepsIsRejected() { + assertRejected(steppedRestrictionChain(1000, 100)); + } + + @Test + public void testShallowDerivationChainWalkedInStepsIsAccepted() { + walkRoot(steppedRestrictionChain(200, 50)); + } + /** S1 restricts S0, S2 restricts S1, and so on. */ private static String simpleRestrictionChain(int length) { StringBuilder body = new StringBuilder("<xs:element name=\"root\" type=\"tns:S")
