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