This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 833197d84e9a21e8d5dc95ab3a87b8486719e544 Author: opencode <[email protected]> AuthorDate: Sun Oct 18 16:01:40 2026 +0200 Evaluate each expression once for EL method references When building a method reference, AstValue resolved the method from the base and the parameter values, then constructed the method information by calling getMethodInfo, which re-ran the whole resolution: it walked the base property chain again, re-evaluated the parameter expressions and looked the method up again. Expressions with side effects, such as a method call as an argument, were therefore evaluated twice per reference, and the reported method information and annotations could come from a different evaluation than the base and the parameters of the reference. The resolution in getMethodInfo also ran without the values, while the reference itself resolved with them, so an expression whose arguments required coercion or were null reported a method not found error, even though invoking the very same expression succeeded. Construct the method information from the method that the reference already resolved. The base, the coerced parameters, the method information and the annotations now all come from a single evaluation, consistent with how the same expression is invoked. Adds a test that counts the evaluations of the base chain getter and of a method call used as an argument, and asserts the base, the parameters and the method information all come from that single evaluation. The test reports two evaluations of each against the previous implementation. --- java/org/apache/el/parser/AstValue.java | 6 ++- test/org/apache/el/TestMethodExpressionImpl.java | 35 ++++++++++++++++ test/org/apache/el/TesterBeanK.java | 51 ++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/java/org/apache/el/parser/AstValue.java b/java/org/apache/el/parser/AstValue.java index 55013fab88..68d4df7181 100644 --- a/java/org/apache/el/parser/AstValue.java +++ b/java/org/apache/el/parser/AstValue.java @@ -265,7 +265,11 @@ public final class AstValue extends SimpleNode { // Handle varArgs and any coercion required values = convertArgs(ctx, values, m); - return new MethodReference(t.base, getMethodInfo(ctx, types), m.getAnnotations(), values); + // The method information is taken from the method resolved above, so + // that the base chain and the parameters are evaluated only once + MethodInfo mi = new MethodInfo(m.getName(), m.getReturnType(), m.getParameterTypes()); + + return new MethodReference(t.base, mi, m.getAnnotations(), values); } diff --git a/test/org/apache/el/TestMethodExpressionImpl.java b/test/org/apache/el/TestMethodExpressionImpl.java index 50844b14b1..76bab3122d 100644 --- a/test/org/apache/el/TestMethodExpressionImpl.java +++ b/test/org/apache/el/TestMethodExpressionImpl.java @@ -24,6 +24,7 @@ import jakarta.el.ExpressionFactory; import jakarta.el.MethodExpression; import jakarta.el.MethodInfo; import jakarta.el.MethodNotFoundException; +import jakarta.el.MethodReference; import jakarta.el.ValueExpression; import org.junit.Assert; @@ -752,6 +753,40 @@ public class TestMethodExpressionImpl { } + @Test + public void testGetReference01() { + // Building a method reference must evaluate the base chain and the + // parameters exactly once + ELProcessor elp = new ELProcessor(); + TesterBeanK bean = new TesterBeanK(); + TesterBeanK child = new TesterBeanK(); + bean.setChild(child); + elp.defineBean("beanK", bean); + ELContext elpContext = elp.getELManager().getELContext(); + + MethodExpression me = elp.getELManager().getExpressionFactory().createMethodExpression(elpContext, + "${beanK.child.format(beanK.next())}", String.class, null); + + MethodReference ref = me.getMethodReference(elpContext); + + Assert.assertEquals(1, bean.baseCalls); + Assert.assertEquals(1, bean.argCalls); + Assert.assertEquals("format", ref.getMethodInfo().getName()); + // The base, parameters and method information must all come from the + // single evaluation + Assert.assertSame(child, ref.getBase()); + Assert.assertArrayEquals(new Object[] { Integer.valueOf(1) }, ref.getEvaluatedParameters()); + + // Invoke must also evaluate each exactly once + bean.baseCalls = 0; + bean.argCalls = 0; + + Assert.assertEquals("formatted-1", me.invoke(elpContext, null)); + Assert.assertEquals(1, bean.baseCalls); + Assert.assertEquals(1, bean.argCalls); + } + + @Test public void testGetMethodInfo01() throws Exception { MethodExpression me = factory.createMethodExpression(context, "#{beanA.setName('New value')}", null, null); diff --git a/test/org/apache/el/TesterBeanK.java b/test/org/apache/el/TesterBeanK.java new file mode 100644 index 0000000000..a65d4b7aa2 --- /dev/null +++ b/test/org/apache/el/TesterBeanK.java @@ -0,0 +1,51 @@ +/* + * 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.el; + +/* + * Counts how often the getter used for the base chain and the method used as + * a parameter are evaluated. + */ +public class TesterBeanK { + + public int baseCalls = 0; + public int argCalls = 0; + + private TesterBeanK child; + + + public TesterBeanK getChild() { + baseCalls++; + return child; + } + + + public void setChild(TesterBeanK child) { + this.child = child; + } + + + public int next() { + argCalls++; + return argCalls; + } + + + public String format(int value) { + return "formatted-" + value; + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
