This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ws-xmlschema.git


The following commit(s) were added to refs/heads/master by this push:
     new 7a3e24d8 Use an explicit heap stack in SaxWalkerOverDom (#124)
7a3e24d8 is described below

commit 7a3e24d89f293be23be30badacb4f1fa233ede31
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Fri Aug 21 17:35:36 2026 +0100

    Use an explicit heap stack in SaxWalkerOverDom (#124)
---
 .../commons/schema/docpath/SaxWalkerOverDom.java   | 77 ++++++++++++++++------
 .../schema/docpath/TestSaxWalkerOverDom.java       |  1 -
 .../docpath/TestSaxWalkerOverDomDeepNesting.java   | 75 +++++++++++++++++++++
 3 files changed, 133 insertions(+), 20 deletions(-)

diff --git 
a/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/docpath/SaxWalkerOverDom.java
 
b/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/docpath/SaxWalkerOverDom.java
index 1e1098ff..c8d5aefb 100644
--- 
a/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/docpath/SaxWalkerOverDom.java
+++ 
b/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/docpath/SaxWalkerOverDom.java
@@ -322,7 +322,44 @@ public final class SaxWalkerOverDom {
         }
     }
 
+    /*
+     * Walks the element depth-first using an explicit heap stack, so JVM stack
+     * consumption is constant in the nesting depth of the document.
+     */
     private void walk(Element element) throws SAXException {
+        final List<ElementFrame> walkStack = new ArrayList<ElementFrame>();
+        walkStack.add(startElement(element));
+
+        while (!walkStack.isEmpty()) {
+            final ElementFrame frame = walkStack.get(walkStack.size() - 1);
+
+            boolean descended = false;
+            while (frame.childIndex < frame.children.getLength()) {
+                final Node node = frame.children.item(frame.childIndex);
+                ++frame.childIndex;
+
+                if (node instanceof Element) {
+                    walkStack.add(startElement((Element)node));
+                    descended = true;
+                    break;
+                } else if (node instanceof Text) {
+                    walk((Text)node);
+                } else if (node instanceof org.w3c.dom.Comment) {
+                    // Ignored.
+                } else {
+                    throw new SAXException("Unrecognized child of " + 
frame.element.getTagName()
+                                           + " of type " + 
node.getClass().getName());
+                }
+            }
+
+            if (!descended) {
+                endElement(frame);
+                walkStack.remove(walkStack.size() - 1);
+            }
+        }
+    }
+
+    private ElementFrame startElement(Element element) throws SAXException {
         DomAttrsAsSax attrs = new DomAttrsAsSax(element.getAttributes());
 
         final List<String> prefixes = startPrefixMappings(element);
@@ -333,33 +370,35 @@ public final class SaxWalkerOverDom {
                                   
convertNullToEmptyString(element.getNodeName()), attrs);
         }
 
-        NodeList children = element.getChildNodes();
-
-        for (int childIndex = 0; childIndex < children.getLength(); 
++childIndex) {
-            Node node = children.item(childIndex);
-            if (node instanceof Element) {
-                walk((Element)node);
-            } else if (node instanceof Text) {
-                walk((Text)node);
-            } else if (node instanceof org.w3c.dom.Comment) {
-                // Ignored.
-            } else {
-                throw new SAXException("Unrecognized child of " + 
element.getTagName() + " of type "
-                                       + node.getClass().getName());
-            }
-        }
+        return new ElementFrame(element, prefixes);
+    }
 
+    private void endElement(ElementFrame frame) throws SAXException {
         for (ContentHandler listener : listeners) {
-            
listener.endElement(convertNullToEmptyString(element.getNamespaceURI()),
-                                
convertNullToEmptyString(element.getLocalName()),
-                                
convertNullToEmptyString(element.getNodeName()));
+            
listener.endElement(convertNullToEmptyString(frame.element.getNamespaceURI()),
+                                
convertNullToEmptyString(frame.element.getLocalName()),
+                                
convertNullToEmptyString(frame.element.getNodeName()));
 
-            for (String prefix : prefixes) {
+            for (String prefix : frame.prefixes) {
                 listener.endPrefixMapping(prefix);
             }
         }
     }
 
+    private static final class ElementFrame {
+        private final Element element;
+        private final NodeList children;
+        private final List<String> prefixes;
+        private int childIndex;
+
+        ElementFrame(Element element, List<String> prefixes) {
+            this.element = element;
+            this.prefixes = prefixes;
+            this.children = element.getChildNodes();
+            this.childIndex = 0;
+        }
+    }
+
     private void walk(Text text) throws SAXException {
         /*
          * TODO: getData() may throw a org.w3c.dom.DOMException if the actual
diff --git 
a/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/docpath/TestSaxWalkerOverDom.java
 
b/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/docpath/TestSaxWalkerOverDom.java
index 441d9309..89971ffb 100644
--- 
a/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/docpath/TestSaxWalkerOverDom.java
+++ 
b/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/docpath/TestSaxWalkerOverDom.java
@@ -37,7 +37,6 @@ import org.xml.sax.Locator;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
 
-import org.apache.ws.commons.schema.docpath.SaxWalkerOverDom;
 import org.apache.ws.commons.schema.testutils.UtilsForTests;
 import org.junit.Test;
 
diff --git 
a/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/docpath/TestSaxWalkerOverDomDeepNesting.java
 
b/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/docpath/TestSaxWalkerOverDomDeepNesting.java
new file mode 100644
index 00000000..28082230
--- /dev/null
+++ 
b/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/docpath/TestSaxWalkerOverDomDeepNesting.java
@@ -0,0 +1,75 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ws.commons.schema.docpath;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * SaxWalkerOverDom must use constant JVM stack space for document nesting.
+ */
+public class TestSaxWalkerOverDomDeepNesting extends Assert {
+
+    private static final int DEPTH = 50000;
+
+    @Test
+    public void testDeeplyNestedDocumentDoesNotOverflowTheStack() throws 
Exception {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+        DocumentBuilder builder = factory.newDocumentBuilder();
+        Document document = builder.newDocument();
+
+        Element current = document.createElement("a");
+        document.appendChild(current);
+        for (int index = 1; index < DEPTH; ++index) {
+            Element child = document.createElement("a");
+            current.appendChild(child);
+            current = child;
+        }
+        current.appendChild(document.createTextNode("deepest"));
+
+        final int[] counts = new int[2];
+        SaxWalkerOverDom walker = new SaxWalkerOverDom(new DefaultHandler() {
+            @Override
+            public void startElement(String uri, String localName, String 
qName,
+                                     org.xml.sax.Attributes attributes) {
+                ++counts[0];
+            }
+
+            @Override
+            public void endElement(String uri, String localName, String qName) 
{
+                ++counts[1];
+            }
+        });
+
+        walker.walk(document);
+
+        assertEquals(DEPTH, counts[0]);
+        assertEquals(DEPTH, counts[1]);
+    }
+}
\ No newline at end of file

Reply via email to