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]
