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 377c490db184bf48d51c21fe0e62d0bb399b3d93 Author: opencode <[email protected]> AuthorDate: Sun Oct 18 14:05:30 2026 +0200 Pass empty array for empty EL function varargs The Jakarta EL varargs function support intended to match Java varargs semantics. Two call shapes did not. A varargs-only function called with no arguments received a null array because AstFunction built the reflective argument array as new Object[] { null }. A mixed fixed and varargs function called with only the fixed arguments received a length-1 varargs array holding a spurious element, because the varargs slot was initialised to new Object[] { null } which the subsequent coercion converted to e.g. String[]{ "" } for String components or a null element otherwise. Plain Java always passes an empty array in both cases. Pass an empty array of the varargs parameter's exact component type instead: built with Array.newInstance() for the zero-argument case, which is required because reflection rejects an Object[] argument for a typed varargs parameter such as String... (the array previously held a null element so the mismatch never surfaced), and as an empty Object[] that the existing coercion converts to the component type for the mixed case. This makes functions behave like direct Java calls, e.g. a format function on String.format(String,Object...) now raises the same MissingFormatArgumentException that String.format("%s") does, rather than rendering a phantom null element. The existing BZ 60431 tests still pass; they could not detect the deviation because the spurious element was invisible to their string concatenation assertions. Adds tester methods and tests that report varargs array length, so a null array (-1) and a spurious element (length 1) are now distinguishable from the correct empty array (length 0). --- java/org/apache/el/parser/AstFunction.java | 9 +++++++-- test/org/apache/el/TesterFunctions.java | 10 +++++++++ test/org/apache/el/parser/TestAstFunction.java | 28 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/java/org/apache/el/parser/AstFunction.java b/java/org/apache/el/parser/AstFunction.java index 7bfa0e5d3a..1c2e5e61ba 100644 --- a/java/org/apache/el/parser/AstFunction.java +++ b/java/org/apache/el/parser/AstFunction.java @@ -17,6 +17,7 @@ /* Generated By:JJTree: Do not edit this line. AstFunction.java */ package org.apache.el.parser; +import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -156,14 +157,18 @@ public final class AstFunction extends SimpleNode { int inputParameterCount = parameters.jjtGetNumChildren(); int methodParameterCount = paramTypes.length; if (inputParameterCount == 0 && methodParameterCount == 1 && m.isVarArgs()) { - params = new Object[] { null }; + // Java semantics: an empty array of the varargs component type is + // passed when no varargs elements are supplied + params = new Object[] { Array.newInstance(paramTypes[0].getComponentType(), 0) }; } else if (inputParameterCount > 0) { params = new Object[methodParameterCount]; try { for (int i = 0; i < methodParameterCount; i++) { if (m.isVarArgs() && i == methodParameterCount - 1) { if (inputParameterCount < methodParameterCount) { - params[i] = new Object[] { null }; + // 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); diff --git a/test/org/apache/el/TesterFunctions.java b/test/org/apache/el/TesterFunctions.java index 904fba0c89..c7f1994f5a 100644 --- a/test/org/apache/el/TesterFunctions.java +++ b/test/org/apache/el/TesterFunctions.java @@ -44,6 +44,16 @@ public class TesterFunctions { return result.toString(); } + public static int varargsLength(String... inputs) { + // Distinguishes a null varargs array from an empty one + return inputs == null ? -1 : inputs.length; + } + + public static int varargsLength2(String prefix, String... inputs) { + // Distinguishes a null varargs array from an empty one + return inputs == null ? -1 : inputs.length; + } + public static String[] toArray(String a, String b) { return new String[] { a, b }; } diff --git a/test/org/apache/el/parser/TestAstFunction.java b/test/org/apache/el/parser/TestAstFunction.java index 4b3a6572bd..e0ae0f6be8 100644 --- a/test/org/apache/el/parser/TestAstFunction.java +++ b/test/org/apache/el/parser/TestAstFunction.java @@ -24,6 +24,8 @@ import jakarta.el.StandardELContext; import org.junit.Assert; import org.junit.Test; +import org.apache.el.TesterFunctions; + public class TestAstFunction { @Test @@ -59,4 +61,30 @@ public class TestAstFunction { result = factory.createValueExpression(context, "${fn:format('%s','one')}", String.class).getValue(context); Assert.assertEquals("one", result); } + + @Test + public void testVarargMethodEmptyVarargs01() throws NoSuchMethodException { + // Java passes an empty array, never null, to a varargs method that is + // called with no arguments + ExpressionFactory factory = ExpressionFactory.newInstance(); + ELContext context = new StandardELContext(factory); + context.getFunctionMapper().mapFunction("fn", "count", + TesterFunctions.class.getMethod("varargsLength", String[].class)); + + Object result = factory.createValueExpression(context, "${fn:count()}", Integer.class).getValue(context); + Assert.assertEquals(Integer.valueOf(0), result); + } + + @Test + public void testVarargMethodEmptyVarargs02() throws NoSuchMethodException { + // Java passes an empty array, never null, to the varargs parameter of a + // mixed fixed and varargs method that is called with no varargs elements + ExpressionFactory factory = ExpressionFactory.newInstance(); + ELContext context = new StandardELContext(factory); + context.getFunctionMapper().mapFunction("fn", "count2", + TesterFunctions.class.getMethod("varargsLength2", String.class, String[].class)); + + Object result = factory.createValueExpression(context, "${fn:count2('x')}", Integer.class).getValue(context); + Assert.assertEquals(Integer.valueOf(0), result); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
