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")