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

rmaucher pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/9.0.x by this push:
     new 2d6a97c6df Fix evaluation of some lambda expressions
2d6a97c6df is described below

commit 2d6a97c6df0369d4189e747a30eaedca5939fc15
Author: remm <[email protected]>
AuthorDate: Tue Sep 1 11:07:31 2026 +0200

    Fix evaluation of some lambda expressions
    
    Found by code review.
    Co authored with OpenCode.
---
 java/org/apache/el/lang/EvaluationContext.java     | 13 +++-------
 .../el/lang/LambdaExpressionNestedState.java       | 27 +++++++++++++++++--
 java/org/apache/el/parser/AstLambdaExpression.java |  9 ++++---
 .../apache/el/parser/TestAstLambdaExpression.java  | 30 ++++++++++++++++++++++
 webapps/docs/changelog.xml                         |  7 +++++
 5 files changed, 70 insertions(+), 16 deletions(-)

diff --git a/java/org/apache/el/lang/EvaluationContext.java 
b/java/org/apache/el/lang/EvaluationContext.java
index 1eeb8bb838..4fbeea2541 100644
--- a/java/org/apache/el/lang/EvaluationContext.java
+++ b/java/org/apache/el/lang/EvaluationContext.java
@@ -27,8 +27,6 @@ import javax.el.FunctionMapper;
 import javax.el.ImportHandler;
 import javax.el.VariableMapper;
 
-import org.apache.el.util.MessageFactory;
-
 /**
  * Extended EL context that wraps an existing ELContext and provides additional
  * functionality for lambda expression evaluation.
@@ -201,18 +199,13 @@ public final class EvaluationContext extends ELContext {
 
 
     /**
-     * Sets the lambda expression nested state for this context.
+     * Sets the lambda expression nested state for this context. Any existing 
state is replaced. This happens when a
+     * sibling lambda expression (one that is not nested inside the lambda 
expression that created the existing state)
+     * is evaluated during the same evaluation.
      *
      * @param lambdaExpressionNestedState The lambda expression nested state
-     *
-     * @throws IllegalStateException if a nested state has already been set
      */
     public void setLambdaExpressionNestedState(LambdaExpressionNestedState 
lambdaExpressionNestedState) {
-        if (this.lambdaExpressionNestedState != null) {
-            // Should never happen
-            throw new 
IllegalStateException(MessageFactory.get("error.lambda.wrongNestedState"));
-        }
-
         this.lambdaExpressionNestedState = lambdaExpressionNestedState;
     }
 }
diff --git a/java/org/apache/el/lang/LambdaExpressionNestedState.java 
b/java/org/apache/el/lang/LambdaExpressionNestedState.java
index 32b3cf27ed..4839315508 100644
--- a/java/org/apache/el/lang/LambdaExpressionNestedState.java
+++ b/java/org/apache/el/lang/LambdaExpressionNestedState.java
@@ -16,6 +16,8 @@
  */
 package org.apache.el.lang;
 
+import org.apache.el.parser.Node;
+
 /**
  * Stores the state required for correct evaluation of lambda expressions. 
Lambda expressions may be nested. Correct
  * evaluation requires knowledge not just of the current lambda expression, 
but also of any nested and nesting
@@ -27,13 +29,34 @@ package org.apache.el.lang;
  */
 public final class LambdaExpressionNestedState {
 
+    private final Node root;
+
     private int nestingCount = 0;
     private boolean hasFormalParameters = false;
 
     /**
-     * Default constructor.
+     * Constructor.
+     *
+     * @param root The lambda expression node that created this state
+     */
+    public LambdaExpressionNestedState(Node root) {
+        this.root = root;
+    }
+
+    /**
+     * Returns whether the given node is the node that created this state or a 
descendant of that node.
+     *
+     * @param node The node to check
+     *
+     * @return {@code true} if the given node is the node that created this 
state or a descendant of that node
      */
-    public LambdaExpressionNestedState() {
+    public boolean contains(Node node) {
+        for (Node n = node; n != null; n = n.jjtGetParent()) {
+            if (n == root) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
diff --git a/java/org/apache/el/parser/AstLambdaExpression.java 
b/java/org/apache/el/parser/AstLambdaExpression.java
index f373131b37..db7303246b 100644
--- a/java/org/apache/el/parser/AstLambdaExpression.java
+++ b/java/org/apache/el/parser/AstLambdaExpression.java
@@ -41,10 +41,11 @@ public class AstLambdaExpression extends SimpleNode {
         // Correct evaluation requires knowledge of the whole set of nested
         // expressions, not just the current expression
         LambdaExpressionNestedState state = 
ctx.getLambdaExpressionNestedState();
-        if (state == null) {
-            // This must be an outer lambda expression. Create and populate the
-            // state.
-            state = new LambdaExpressionNestedState();
+        if (state == null || !state.contains(this)) {
+            // This is an outer lambda expression or a sibling of the lambda
+            // expression that created the stored state. Create and populate
+            // the state.
+            state = new LambdaExpressionNestedState(this);
             populateNestedState(state);
             ctx.setLambdaExpressionNestedState(state);
         }
diff --git a/test/org/apache/el/parser/TestAstLambdaExpression.java 
b/test/org/apache/el/parser/TestAstLambdaExpression.java
index 75a02f4e02..94936925a8 100644
--- a/test/org/apache/el/parser/TestAstLambdaExpression.java
+++ b/test/org/apache/el/parser/TestAstLambdaExpression.java
@@ -241,4 +241,34 @@ public class TestAstLambdaExpression {
                 processor.getValue("foo:v = (x)->x+1; foo:v(0)", 
Integer.class);
         Assert.assertEquals(Integer.valueOf(1), result);
     }
+
+
+    @Test
+    public void testSiblings01() {
+        ELProcessor processor = new ELProcessor();
+        // The second sibling lambda expression must not be checked against the
+        // nested state created by the first sibling
+        Object result = processor.getValue("(c->c)(3) + (a->b->b)(1)(2)", 
Integer.class);
+        Assert.assertEquals(Integer.valueOf(5), result);
+    }
+
+
+    @Test(expected=ELException.class)
+    public void testSiblings02() {
+        ELProcessor processor = new ELProcessor();
+        // The second sibling lambda expression has more sets of method
+        // parameters than it has nested lambda expressions. The nested state
+        // created by the first sibling must not mask the error.
+        processor.getValue("(a->b->b)(1)(2) + (c->c)(3)(4)", Integer.class);
+    }
+
+
+    @Test
+    public void testSiblings03() {
+        ELProcessor processor = new ELProcessor();
+        // The second sibling lambda expression must be auto-invoked. The
+        // nested state created by the first sibling must not block it.
+        Object result = processor.getValue("(c->c)(3) + (()->21)", 
Integer.class);
+        Assert.assertEquals(Integer.valueOf(24), result);
+    }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index d46ec0ee09..c987996a9c 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -208,6 +208,13 @@
       </fix>
     </changelog>
   </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        Fix EL evaluation of some lambda expressions. (remm)
+      </fix>
+    </changelog>
+  </subsection>
   <subsection name="Cluster">
     <changelog>
       <fix>


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

Reply via email to