You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ws.apache.org by co...@apache.org on 2026/09/24 15:46:01 UTC

(ws-xmlschema) 01/01: Count a cached base type's whole derivation chain against the walk depth

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 <co...@apache.org>
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")