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
commit 58eac76d9bd610a721cef94e4cb32bcc8a49e76c 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 208c22a714..83a0a9ae68 100644 --- a/java/org/apache/el/MethodExpressionImpl.java +++ b/java/org/apache/el/MethodExpressionImpl.java @@ -127,7 +127,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()); } /** @@ -206,7 +213,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 6606ec9627..497ab083dd 100644 --- a/java/org/apache/el/MethodExpressionLiteral.java +++ b/java/org/apache/el/MethodExpressionLiteral.java @@ -87,7 +87,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 b47746abb6..cc5cb11186 100644 --- a/test/org/apache/el/TestMethodExpressionImpl.java +++ b/test/org/apache/el/TestMethodExpressionImpl.java @@ -744,4 +744,73 @@ public class TestMethodExpressionImpl { 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]
