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]

Reply via email to