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

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

commit 3d092e70a4e5c2a07155cbdeee4ec0d5a4536023
Author: opencode <[email protected]>
AuthorDate: Sun Oct 18 12:45:43 2026 +0200

    Correct equals() implementations in method expression classes
    
    MethodExpressionImpl.equals() and MethodExpressionLiteral.equals()
    previously decided equality by comparing only the hashCode() of the
    expression string. Distinct expressions whose expression strings
    happened to have colliding 32-bit hash codes were therefore reported
    as equal, violating the equals() contract and the documented
    jakarta.el.Expression "identical parsed representation" semantics.
    
    MethodExpressionImpl now mirrors ValueExpressionImpl: equals() uses
    the hash code only as a fast path filter and then compares the parsed
    AST nodes (SimpleNode provides semantic equals()/hashCode() based on
    node type, image and children), with hashCode() derived from the
    parsed node so the equals()/hashCode() contract holds, including for
    expressions that differ only in whitespace. MethodExpressionLiteral
    now compares the literal expression string directly.
    
    Adds unit tests covering identical expressions, whitespace variants,
    different expressions and the classic "Aa"/"BB" string hash-collision
    pair for both classes.
---
 java/org/apache/el/MethodExpressionImpl.java     | 11 +++-
 java/org/apache/el/MethodExpressionLiteral.java  |  6 ++-
 test/org/apache/el/TestMethodExpressionImpl.java | 69 ++++++++++++++++++++++++
 3 files changed, 83 insertions(+), 3 deletions(-)

diff --git a/java/org/apache/el/MethodExpressionImpl.java 
b/java/org/apache/el/MethodExpressionImpl.java
index 8e0b7ed897..190e281aef 100644
--- a/java/org/apache/el/MethodExpressionImpl.java
+++ b/java/org/apache/el/MethodExpressionImpl.java
@@ -128,7 +128,14 @@ public final class MethodExpressionImpl extends 
MethodExpression implements Exte
      */
     @Override
     public boolean equals(Object obj) {
-        return (obj instanceof MethodExpressionImpl && obj.hashCode() == 
this.hashCode());
+        if (!(obj instanceof MethodExpressionImpl)) {
+            return false;
+        }
+        if (obj.hashCode() != this.hashCode()) {
+            return false;
+        }
+
+        return this.getNode().equals(((MethodExpressionImpl) obj).getNode());
     }
 
     /**
@@ -207,7 +214,7 @@ public final class MethodExpressionImpl extends 
MethodExpression implements Exte
      */
     @Override
     public int hashCode() {
-        return this.expr.hashCode();
+        return this.getNode().hashCode();
     }
 
     /**
diff --git a/java/org/apache/el/MethodExpressionLiteral.java 
b/java/org/apache/el/MethodExpressionLiteral.java
index 8e8f68eda7..09846c7b99 100644
--- a/java/org/apache/el/MethodExpressionLiteral.java
+++ b/java/org/apache/el/MethodExpressionLiteral.java
@@ -111,7 +111,11 @@ public class MethodExpressionLiteral extends 
MethodExpression implements Externa
 
     @Override
     public boolean equals(Object obj) {
-        return (obj instanceof MethodExpressionLiteral && this.hashCode() == 
obj.hashCode());
+        if (!(obj instanceof MethodExpressionLiteral)) {
+            return false;
+        }
+
+        return this.expr.equals(((MethodExpressionLiteral) obj).expr);
     }
 
     @Override
diff --git a/test/org/apache/el/TestMethodExpressionImpl.java 
b/test/org/apache/el/TestMethodExpressionImpl.java
index ae4c53bd7b..cf2e65b9b1 100644
--- a/test/org/apache/el/TestMethodExpressionImpl.java
+++ b/test/org/apache/el/TestMethodExpressionImpl.java
@@ -747,4 +747,73 @@ public class TestMethodExpressionImpl extends ELBaseTest {
         Assert.assertEquals(1, mi.getParamTypes().length);
         Assert.assertEquals(String.class, mi.getParamTypes()[0]);
     }
+
+
+    @Test
+    public void testEqualsHashCodeSameExpression() {
+        MethodExpression me1 = factory.createMethodExpression(context, 
"${beanB.getName}", String.class,
+                new Class<?>[] {});
+        MethodExpression me2 = factory.createMethodExpression(context, 
"${beanB.getName}", String.class,
+                new Class<?>[] {});
+
+        Assert.assertEquals(me1, me2);
+        Assert.assertEquals(me1.hashCode(), me2.hashCode());
+    }
+
+
+    @Test
+    public void testEqualsHashCodeWhitespaceVariants() {
+        MethodExpression me1 = factory.createMethodExpression(context, 
"${beanB.getName}", String.class,
+                new Class<?>[] {});
+        MethodExpression me2 = factory.createMethodExpression(context, "${ 
beanB.getName }", String.class,
+                new Class<?>[] {});
+
+        Assert.assertEquals(me1, me2);
+        Assert.assertEquals(me1.hashCode(), me2.hashCode());
+    }
+
+
+    @Test
+    public void testNotEqualsDifferentExpression() {
+        MethodExpression me1 = factory.createMethodExpression(context, 
"${beanB.getName}", String.class,
+                new Class<?>[] {});
+        MethodExpression me2 = factory.createMethodExpression(context, 
"${beanBB.getName}", String.class,
+                new Class<?>[] {});
+
+        Assert.assertNotEquals(me1, me2);
+    }
+
+
+    @Test
+    public void testNotEqualsHashCollidingExpressions() {
+        // "Aa" and "BB" are a well known pair of strings with the same hash 
code
+        Assert.assertEquals("Aa".hashCode(), "BB".hashCode());
+
+        MethodExpression me1 = factory.createMethodExpression(context, 
"${Aa}", String.class, new Class<?>[] {});
+        MethodExpression me2 = factory.createMethodExpression(context, 
"${BB}", String.class, new Class<?>[] {});
+
+        Assert.assertNotEquals(me1, me2);
+    }
+
+
+    @Test
+    public void testEqualsHashCodeSameLiteral() {
+        MethodExpression me1 = factory.createMethodExpression(context, "'Aa'", 
String.class, new Class<?>[] {});
+        MethodExpression me2 = factory.createMethodExpression(context, "'Aa'", 
String.class, new Class<?>[] {});
+
+        Assert.assertEquals(me1, me2);
+        Assert.assertEquals(me1.hashCode(), me2.hashCode());
+    }
+
+
+    @Test
+    public void testNotEqualsHashCollidingLiterals() {
+        // "Aa" and "BB" are a well known pair of strings with the same hash 
code
+        Assert.assertEquals("'Aa'".hashCode(), "'BB'".hashCode());
+
+        MethodExpression me1 = factory.createMethodExpression(context, "'Aa'", 
String.class, new Class<?>[] {});
+        MethodExpression me2 = factory.createMethodExpression(context, "'BB'", 
String.class, new Class<?>[] {});
+
+        Assert.assertNotEquals(me1, me2);
+    }
 }


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

Reply via email to