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

markt-asf pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/11.0.x by this push:
     new 62dcff2b0b Review "Validate begin, end and step in the ForEach and 
ForTokens..."
62dcff2b0b is described below

commit 62dcff2b0b084c9543a6ff212017b49b6afdc124
Author: Mark Thomas <[email protected]>
AuthorDate: Fri Nov 6 10:53:22 2026 +0000

    Review "Validate begin, end and step in the ForEach and ForTokens..."
    
    Add handling for end > begin
    Reduce code duplication
    
    Co-authored by: Claude / Sonnet 5.5
    Co-authored by: Codex / GPT-6.1-Sol
---
 .../jasper/tagplugins/jstl/core/ForEach.java       | 26 +++++----
 .../jasper/tagplugins/jstl/core/ForTokens.java     |  9 +--
 .../tagplugins/jstl/core/LoopAttributeChecks.java  | 68 ++++++++++++++++++++++
 .../jasper/tagplugins/jstl/core/TestForEach.java   | 16 +++++
 test/webapp/jstl/foreach-begin-after-end.jsp       | 40 +++++++++++++
 5 files changed, 141 insertions(+), 18 deletions(-)

diff --git a/java/org/apache/jasper/tagplugins/jstl/core/ForEach.java 
b/java/org/apache/jasper/tagplugins/jstl/core/ForEach.java
index 57353edcde..e041ad6cf9 100644
--- a/java/org/apache/jasper/tagplugins/jstl/core/ForEach.java
+++ b/java/org/apache/jasper/tagplugins/jstl/core/ForEach.java
@@ -69,8 +69,7 @@ public final class ForEach implements TagPlugin {
         }
         ctxt.generateJavaSource(";");
         if (hasBegin) {
-            ctxt.generateJavaSource("if (" + beginName + " < 0) {" +
-                    "throw new jakarta.servlet.jsp.JspTagException(\"'begin' < 
0\");}");
+            LoopAttributeChecks.generateBeginCheck(ctxt, beginName);
         }
 
         String endName = ctxt.getTemporaryVariableName();
@@ -84,8 +83,7 @@ public final class ForEach implements TagPlugin {
         }
         ctxt.generateJavaSource(";");
         if (hasEnd) {
-            ctxt.generateJavaSource("if (" + endName + " < 0) {" +
-                    "throw new jakarta.servlet.jsp.JspTagException(\"'end' < 
0\");}");
+            LoopAttributeChecks.generateEndCheck(ctxt, endName);
         }
 
         String stepName = null;
@@ -94,8 +92,7 @@ public final class ForEach implements TagPlugin {
             ctxt.generateJavaSource("int " + stepName + " = ");
             ctxt.generateAttribute("step");
             ctxt.generateJavaSource(";");
-            ctxt.generateJavaSource("if (" + stepName + " <= 0) {" +
-                    "throw new jakarta.servlet.jsp.JspTagException(\"'step' <= 
0\");}");
+            LoopAttributeChecks.generateStepCheck(ctxt, stepName);
         }
 
         ctxt.generateJavaSource("for (int " + index + " = " + beginName + "; " 
+ index + " <= " + endName + ";");
@@ -136,8 +133,7 @@ public final class ForEach implements TagPlugin {
             ctxt.generateJavaSource("int " + beginV + " = ");
             ctxt.generateAttribute("begin");
             ctxt.generateJavaSource(";");
-            ctxt.generateJavaSource("if (" + beginV + " < 0) {" +
-                    "throw new jakarta.servlet.jsp.JspTagException(\"'begin' < 
0\");}");
+            LoopAttributeChecks.generateBeginCheck(ctxt, beginV);
         }
         if (hasEnd) {
             indexV = ctxt.getTemporaryVariableName();
@@ -146,16 +142,19 @@ public final class ForEach implements TagPlugin {
             ctxt.generateJavaSource("int " + endV + " = ");
             ctxt.generateAttribute("end");
             ctxt.generateJavaSource(";");
-            ctxt.generateJavaSource("if (" + endV + " < 0) {" +
-                    "throw new jakarta.servlet.jsp.JspTagException(\"'end' < 
0\");}");
+            LoopAttributeChecks.generateEndCheck(ctxt, endV);
         }
         if (hasStep) {
             stepV = ctxt.getTemporaryVariableName();
             ctxt.generateJavaSource("int " + stepV + " = ");
             ctxt.generateAttribute("step");
             ctxt.generateJavaSource(";");
-            ctxt.generateJavaSource("if (" + stepV + " <= 0) {" +
-                    "throw new jakarta.servlet.jsp.JspTagException(\"'step' <= 
0\");}");
+            LoopAttributeChecks.generateStepCheck(ctxt, stepV);
+        }
+
+        // An empty range must not execute the body or consume any items.
+        if (hasBegin && hasEnd) {
+            ctxt.generateJavaSource("if (" + beginV + " <= " + endV + ") {");
         }
 
         String iterV = ctxt.getTemporaryVariableName();
@@ -248,6 +247,9 @@ public final class ForEach implements TagPlugin {
         }
         ctxt.generateJavaSource("}"); // while
         ctxt.generateJavaSource("}"); // Not Null
+        if (hasBegin && hasEnd) {
+            ctxt.generateJavaSource("}"); // Non-empty range
+        }
     }
 
     /**
diff --git a/java/org/apache/jasper/tagplugins/jstl/core/ForTokens.java 
b/java/org/apache/jasper/tagplugins/jstl/core/ForTokens.java
index 05cd8f8156..8e70b5dbfa 100644
--- a/java/org/apache/jasper/tagplugins/jstl/core/ForTokens.java
+++ b/java/org/apache/jasper/tagplugins/jstl/core/ForTokens.java
@@ -78,8 +78,7 @@ public class ForTokens implements TagPlugin {
             ctxt.generateJavaSource("int " + beginName + " = ");
             ctxt.generateAttribute("begin");
             ctxt.generateJavaSource(";");
-            ctxt.generateJavaSource("if (" + beginName + " < 0) {" +
-                    "throw new jakarta.servlet.jsp.JspTagException(\"'begin' < 
0\");}");
+            LoopAttributeChecks.generateBeginCheck(ctxt, beginName);
             ctxt.generateJavaSource("for(int " + index + " = 0; " + index + " 
< " + beginName + " && " + stName +
                     ".hasMoreTokens(); " + index + "++, " + stName + 
".nextToken()){}");
         } else {
@@ -94,8 +93,7 @@ public class ForTokens implements TagPlugin {
             ctxt.generateJavaSource("int " + endSpecifiedName + " = ");
             ctxt.generateAttribute("end");
             ctxt.generateJavaSource(";");
-            ctxt.generateJavaSource("if (" + endSpecifiedName + " < 0) {" +
-                    "throw new jakarta.servlet.jsp.JspTagException(\"'end' < 
0\");}");
+            LoopAttributeChecks.generateEndCheck(ctxt, endSpecifiedName);
             ctxt.generateJavaSource("int " + endName + " = (" + 
tokensCountName + " - 1) < " + endSpecifiedName +
                     " ? " + tokensCountName + " - 1 : " + endSpecifiedName + 
";");
         } else {
@@ -108,8 +106,7 @@ public class ForTokens implements TagPlugin {
             ctxt.generateJavaSource("int " + stepName + " = ");
             ctxt.generateAttribute("step");
             ctxt.generateJavaSource(";");
-            ctxt.generateJavaSource("if (" + stepName + " <= 0) {" +
-                    "throw new jakarta.servlet.jsp.JspTagException(\"'step' <= 
0\");}");
+            LoopAttributeChecks.generateStepCheck(ctxt, stepName);
         } else {
             ctxt.generateJavaSource("int " + stepName + " = 1;");
         }
diff --git 
a/java/org/apache/jasper/tagplugins/jstl/core/LoopAttributeChecks.java 
b/java/org/apache/jasper/tagplugins/jstl/core/LoopAttributeChecks.java
new file mode 100644
index 0000000000..c358152007
--- /dev/null
+++ b/java/org/apache/jasper/tagplugins/jstl/core/LoopAttributeChecks.java
@@ -0,0 +1,68 @@
+/*
+ * 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.jasper.tagplugins.jstl.core;
+
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+
+/**
+ * Generates the validation of the <code>begin</code>, <code>end</code> and 
<code>step</code> loop control attributes
+ * shared by the &lt;c:forEach&gt; and &lt;c:forTokens&gt; tag plugins. The 
checks and messages match the setters of the
+ * reference implementation, <code>LoopTagSupport</code>.
+ */
+final class LoopAttributeChecks {
+
+    private LoopAttributeChecks() {
+        // Utility class. Hide default constructor
+    }
+
+    /**
+     * Generates a check that throws a <code>JspTagException</code> if the 
named <code>begin</code> variable is
+     * negative.
+     *
+     * @param ctxt The tag plugin context
+     * @param variable The name of the generated variable holding the value of 
the attribute
+     */
+    static void generateBeginCheck(TagPluginContext ctxt, String variable) {
+        generateCheck(ctxt, variable, "<", "'begin' < 0");
+    }
+
+    /**
+     * Generates a check that throws a <code>JspTagException</code> if the 
named <code>end</code> variable is negative.
+     *
+     * @param ctxt The tag plugin context
+     * @param variable The name of the generated variable holding the value of 
the attribute
+     */
+    static void generateEndCheck(TagPluginContext ctxt, String variable) {
+        generateCheck(ctxt, variable, "<", "'end' < 0");
+    }
+
+    /**
+     * Generates a check that throws a <code>JspTagException</code> if the 
named <code>step</code> variable is not
+     * positive.
+     *
+     * @param ctxt The tag plugin context
+     * @param variable The name of the generated variable holding the value of 
the attribute
+     */
+    static void generateStepCheck(TagPluginContext ctxt, String variable) {
+        generateCheck(ctxt, variable, "<=", "'step' <= 0");
+    }
+
+    private static void generateCheck(TagPluginContext ctxt, String variable, 
String operator, String message) {
+        ctxt.generateJavaSource("if (" + variable + " " + operator + " 0) {" +
+                "throw new jakarta.servlet.jsp.JspTagException(\"" + message + 
"\");}");
+    }
+}
diff --git a/test/org/apache/jasper/tagplugins/jstl/core/TestForEach.java 
b/test/org/apache/jasper/tagplugins/jstl/core/TestForEach.java
index 50905ff2cb..3c9405b87e 100644
--- a/test/org/apache/jasper/tagplugins/jstl/core/TestForEach.java
+++ b/test/org/apache/jasper/tagplugins/jstl/core/TestForEach.java
@@ -74,6 +74,22 @@ public class TestForEach extends AbstractTestTag {
     }
 
 
+    @Test
+    public void testCollectionBeginAfterEnd() throws Exception {
+        ByteChunk res = new ByteChunk();
+
+        int rc = getUrl("http://localhost:"; + getPort() +
+                "/test/jstl/foreach-begin-after-end.jsp", res, null);
+
+        Assert.assertEquals(HttpServletResponse.SC_OK, rc);
+        String body = res.toString();
+        Assert.assertFalse(body, body.contains("FAIL"));
+        Assert.assertTrue(body, body.contains("iteratorNext=a"));
+        Assert.assertTrue(body, body.contains("equal=b"));
+        Assert.assertTrue(body, body.contains("zero=a"));
+    }
+
+
     /*
      * The reference implementation validates the begin, end and step
      * attributes in their setters, throwing a JspTagException. The plugin
diff --git a/test/webapp/jstl/foreach-begin-after-end.jsp 
b/test/webapp/jstl/foreach-begin-after-end.jsp
new file mode 100644
index 0000000000..bd3f7c29bb
--- /dev/null
+++ b/test/webapp/jstl/foreach-begin-after-end.jsp
@@ -0,0 +1,40 @@
+<%--
+ 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.
+--%>
+<%@ page session="false" contentType="text/html; charset=UTF-8"
+         pageEncoding="UTF-8" import="java.util.Iterator,java.util.List" %>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/core"; prefix="c" %>
+<%
+    Iterator<String> iterator = List.of("a", "b", "c", "d").iterator();
+    pageContext.setAttribute("values", iterator);
+    pageContext.setAttribute("array", new String[] { "a", "b", "c", "d" });
+%>
+<c:forEach items="${['a','b','c','d']}" begin="2" end="1" var="i">
+    <p>FAIL - list body executed</p>
+</c:forEach>
+<c:forEach items="${array}" begin="2" end="1" var="i">
+    <p>FAIL - array body executed</p>
+</c:forEach>
+<c:forEach items="${values}" begin="2" end="1" var="i">
+    <p>FAIL - iterator body executed</p>
+</c:forEach>
+<p>iteratorNext=<%= iterator.next() %></p>
+<c:forEach items="${['a','b','c']}" begin="1" end="1" var="i">
+    <p>equal=${i}</p>
+</c:forEach>
+<c:forEach items="${['a','b','c']}" end="0" var="i">
+    <p>zero=${i}</p>
+</c:forEach>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to