From 9438d6eb9c8ad8ca30dee55d8a53f3ea1a90c598 Mon Sep 17 00:00:00 2001 From: Mykola Pavluchynskyi Date: Thu, 29 Oct 2026 22:10:39 +0100 Subject: [PATCH] Fix BZ 70297 - Evaluate a single EL function varargs argument once When an EL function whose method is varargs was called with exactly one argument for the varargs parameter, AstFunction evaluated that argument twice: once to check whether it is already an array and once more for the value. An argument with side effects ran twice, the function received the result of the second evaluation, and an expensive argument cost double. Evaluate the argument once and use the value both for the array check and as the single varargs element. Calls with two or more varargs values and the single array argument case are unchanged. Adds a test that counts the evaluations of a method call used as the single varargs argument. The test reports two evaluations against the previous implementation. --- java/org/apache/el/parser/AstFunction.java | 13 ++++++++++--- test/org/apache/el/parser/TestAstFunction.java | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/java/org/apache/el/parser/AstFunction.java b/java/org/apache/el/parser/AstFunction.java index 2621d42c2bf0..edac458c2059 100644 --- a/java/org/apache/el/parser/AstFunction.java +++ b/java/org/apache/el/parser/AstFunction.java @@ -170,9 +170,16 @@ public Object getValue(EvaluationContext ctx) throws ELException { // Java semantics: an empty array is passed when no // varargs elements are supplied params[i] = new Object[0]; - } else if (inputParameterCount == methodParameterCount && - isArray(parameters.jjtGetChild(i).getValue(ctx))) { - params[i] = parameters.jjtGetChild(i).getValue(ctx); + } else if (inputParameterCount == methodParameterCount) { + // Evaluate the single argument once: it is either + // the varargs array itself or its only element + Object value = parameters.jjtGetChild(i).getValue(ctx); + if (isArray(value)) { + params[i] = value; + } else { + params[i] = new Object[] { ELSupport.coerceToType(ctx, value, + paramTypes[i].getComponentType()) }; + } } else { Object[] varargs = new Object[inputParameterCount - methodParameterCount + 1]; Class target = paramTypes[i].getComponentType(); diff --git a/test/org/apache/el/parser/TestAstFunction.java b/test/org/apache/el/parser/TestAstFunction.java index 2137afe56513..7604c891445d 100644 --- a/test/org/apache/el/parser/TestAstFunction.java +++ b/test/org/apache/el/parser/TestAstFunction.java @@ -25,6 +25,7 @@ import org.junit.Assert; import org.junit.Test; +import org.apache.el.TesterBeanK; import org.apache.el.TesterFunctions; public class TestAstFunction { @@ -108,6 +109,19 @@ public void testVarargMethod() throws NoSuchMethodException, SecurityException { Assert.assertEquals("one", result); } + @Test + public void testVarargMethodSingleArgumentEvaluatedOnce() throws NoSuchMethodException { + // The argument that alone fills the varargs parameter must be evaluated + // exactly once, as each argument is when there are several + ELProcessor processor = new ELProcessor(); + TesterBeanK bean = new TesterBeanK(); + processor.defineBean("beanK", bean); + processor.defineFunction("fn", "format", String.class.getMethod("format", String.class, Object[].class)); + + Assert.assertEquals("1", processor.getValue("fn:format('%s', beanK.next())", String.class)); + Assert.assertEquals(1, bean.argCalls); + } + @Test public void testVarargMethodEmptyVarargs01() throws NoSuchMethodException { // Java passes an empty array, never null, to a varargs method that is