You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by re...@apache.org on 2026/10/14 20:26:55 UTC
(tomcat) 03/11: Signal EmptyStackException for a negative ArrayStack peek index
This is an automated email from the ASF dual-hosted git repository.
rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 6eb71c44c0bc24d33c135d67233a181b6822b186
Author: opencode <re...@apache.org>
AuthorDate: Wed Oct 14 16:58:30 2026 +0200
Signal EmptyStackException for a negative ArrayStack peek index
ArrayStack.peek(int) computed [size() - n - 1] and threw
EmptyStackException only when that index fell below zero, which covers
indexes beyond the bottom of the stack. A negative index moved the
computed position beyond the end of the backing list instead, so get()
threw an IndexOutOfBoundsException.
That exception escaped Digester.peek(int), which only catches
EmptyStackException, even though the method is documented to return
null for any out of range index. One path where a negative index can be
passed in is CallMethodRule with a negative targetOffset deeper than
the current stack depth, where digester.peek(getCount() + targetOffset)
then reported a wrapped IndexOutOfBoundsException rather than the
intended 'Call target is null' parse error that names the offset and
the stack depth.
Guard both ends of the range instead. For non-negative indexes the
condition is equivalent to the previous one, so no legitimate use
changes, and the outcome for extreme values no longer depends on
integer wrap-around.
Adds TestArrayStack covering the top, the bottom, the empty stack and
both out of range directions including Integer.MIN_VALUE and
Integer.MAX_VALUE, plus a TestDigester test for the peek(int) null
contract for negative indexes.
---
.../apache/tomcat/util/digester/ArrayStack.java | 15 ++--
.../tomcat/util/digester/TestArrayStack.java | 88 ++++++++++++++++++++++
.../apache/tomcat/util/digester/TestDigester.java | 16 ++++
3 files changed, 114 insertions(+), 5 deletions(-)
diff --git a/java/org/apache/tomcat/util/digester/ArrayStack.java b/java/org/apache/tomcat/util/digester/ArrayStack.java
index fb6a533814..5ed71ba933 100644
--- a/java/org/apache/tomcat/util/digester/ArrayStack.java
+++ b/java/org/apache/tomcat/util/digester/ArrayStack.java
@@ -100,15 +100,20 @@ public class ArrayStack<E> extends ArrayList<E> {
*
* @return the n'th item on the stack, zero relative
*
- * @throws EmptyStackException if there are not enough items on the stack to satisfy this request
+ * @throws EmptyStackException if there are not enough items on the stack to satisfy this request, including when
+ * {@code n} is negative. Note that unlike the original Commons Collections code, a
+ * negative index signals EmptyStackException rather than surfacing as an
+ * IndexOutOfBoundsException.
*/
public E peek(int n) throws EmptyStackException {
- int m = (size() - n) - 1;
- if (m < 0) {
+ // A negative index is out of range just like an index beyond the bottom
+ // of the stack. Guarding only the upper bound would let get() throw an
+ // IndexOutOfBoundsException, which callers such as Digester.peek(int)
+ // do not expect.
+ if (n < 0 || n >= size()) {
throw new EmptyStackException();
- } else {
- return get(m);
}
+ return get(size() - 1 - n);
}
/**
diff --git a/test/org/apache/tomcat/util/digester/TestArrayStack.java b/test/org/apache/tomcat/util/digester/TestArrayStack.java
new file mode 100644
index 0000000000..9146b40588
--- /dev/null
+++ b/test/org/apache/tomcat/util/digester/TestArrayStack.java
@@ -0,0 +1,88 @@
+/*
+ * 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.tomcat.util.digester;
+
+import java.util.EmptyStackException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestArrayStack {
+
+ @Test
+ public void testPeekTopAndBottom() {
+ ArrayStack<String> stack = new ArrayStack<>();
+ stack.push("bottom");
+ stack.push("top");
+
+ Assert.assertEquals("top", stack.peek(0));
+ Assert.assertEquals("bottom", stack.peek(1));
+ }
+
+
+ @Test(expected = EmptyStackException.class)
+ public void testPeekEmptyStack() {
+ new ArrayStack<String>().peek(0);
+ }
+
+
+ @Test(expected = EmptyStackException.class)
+ public void testPeekBeyondBottom() {
+ ArrayStack<String> stack = new ArrayStack<>();
+ stack.push("only");
+
+ stack.peek(1);
+ }
+
+
+ /*
+ * A negative index is out of range rather than beyond the end of the
+ * backing list, so it must signal EmptyStackException like the other
+ * out of range indexes.
+ */
+ @Test(expected = EmptyStackException.class)
+ public void testPeekNegativeIndex() {
+ ArrayStack<String> stack = new ArrayStack<>();
+ stack.push("only");
+
+ stack.peek(-1);
+ }
+
+
+ @Test(expected = EmptyStackException.class)
+ public void testPeekNegativeIndexEmptyStack() {
+ new ArrayStack<String>().peek(-1);
+ }
+
+
+ @Test(expected = EmptyStackException.class)
+ public void testPeekIntegerMinValue() {
+ ArrayStack<String> stack = new ArrayStack<>();
+ stack.push("only");
+
+ stack.peek(Integer.MIN_VALUE);
+ }
+
+
+ @Test(expected = EmptyStackException.class)
+ public void testPeekIntegerMaxValue() {
+ ArrayStack<String> stack = new ArrayStack<>();
+ stack.push("only");
+
+ stack.peek(Integer.MAX_VALUE);
+ }
+}
diff --git a/test/org/apache/tomcat/util/digester/TestDigester.java b/test/org/apache/tomcat/util/digester/TestDigester.java
index 25eaf04f66..3f98771dd5 100644
--- a/test/org/apache/tomcat/util/digester/TestDigester.java
+++ b/test/org/apache/tomcat/util/digester/TestDigester.java
@@ -185,4 +185,20 @@ public class TestDigester {
Assert.assertNotNull(digester.createSAXException("message", new RuntimeException("cause")));
}
+
+ /*
+ * peek(int) is documented to return null for any out of range index,
+ * including negative ones such as the values a negative CallMethodRule
+ * target offset can produce.
+ */
+ @Test
+ public void testPeekOutOfRangeIndexReturnsNull() {
+ Digester digester = new Digester();
+ digester.push(new Object());
+
+ Assert.assertNotNull(digester.peek(0));
+ Assert.assertNull(digester.peek(1));
+ Assert.assertNull(digester.peek(-1));
+ }
+
}
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org