This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit b1852c40bc6bf74b92646d8b4db45d010548a9e7 Author: opencode <[email protected]> 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 22fa3e1ac4..3fbabee12e 100644 --- a/java/org/apache/tomcat/util/digester/ArrayStack.java +++ b/java/org/apache/tomcat/util/digester/ArrayStack.java @@ -98,15 +98,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: [email protected] For additional commands, e-mail: [email protected]
