Skip to content

Commit 83ca2c0

Browse files
committed
Support MethodHandle function invocation with varargs array in SpEL
Prior to this commit, the Spring Expression Language (SpEL) could not invoke a varargs MethodHandle function with an array containing the variable arguments, although that is supported for a varargs Method function. Attempting to do so resulted in the array being supplied as a single argument to the MethodHandle. This commit addresses this by updating the executeFunctionViaMethodHandle(...) method in FunctionReference as follows when the user supplies the varargs already packaged in an array. - Creates a new array large enough to hold the non-varargs arguments and the unpackaged varargs arguments. - Adds the non-varargs arguments to the beginning of that array and adds the unpackaged varargs arguments to the end of that array. - Invokes the MethodHandle with the new arguments array. Closes gh-33191
1 parent dc16f3c commit 83ca2c0

File tree

2 files changed

+31
-21
lines changed

2 files changed

+31
-21
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java

+23-4
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,29 @@ else if (spelParamCount != declaredParamCount) {
225225
TypeConverter converter = state.getEvaluationContext().getTypeConverter();
226226
ReflectionHelper.convertAllMethodHandleArguments(converter, functionArgs, methodHandle, varArgPosition);
227227

228-
if (isSuspectedVarargs && declaredParamCount == 1) {
229-
// we only repack the varargs if it is the ONLY argument
230-
functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(
231-
methodHandle.type().parameterArray(), functionArgs);
228+
if (isSuspectedVarargs) {
229+
if (declaredParamCount == 1) {
230+
// We only repackage the varargs if it is the ONLY argument -- for example,
231+
// when we are dealing with a bound MethodHandle.
232+
functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(
233+
methodHandle.type().parameterArray(), functionArgs);
234+
}
235+
else if (spelParamCount == declaredParamCount) {
236+
// If the varargs were supplied already packaged in an array, we have to create
237+
// a new array, add the non-varargs arguments to the beginning of that array,
238+
// and add the unpackaged varargs arguments to the end of that array. The reason
239+
// is that MethodHandle.invokeWithArguments(Object...) does not expect varargs
240+
// to be packaged in an array, in contrast to how method invocation works with
241+
// reflection.
242+
int actualVarargsIndex = functionArgs.length - 1;
243+
if (actualVarargsIndex >= 0 && functionArgs[actualVarargsIndex].getClass().isArray()) {
244+
Object[] argsToUnpack = (Object[]) functionArgs[actualVarargsIndex];
245+
Object[] newArgs = new Object[actualVarargsIndex + argsToUnpack.length];
246+
System.arraycopy(functionArgs, 0, newArgs, 0, actualVarargsIndex);
247+
System.arraycopy(argsToUnpack, 0, newArgs, actualVarargsIndex, argsToUnpack.length);
248+
functionArgs = newArgs;
249+
}
250+
}
232251
}
233252

234253
try {

spring-expression/src/test/java/org/springframework/expression/spel/VariableAndFunctionTests.java

+8-17
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.expression.spel;
1818

19-
import org.junit.jupiter.api.Disabled;
2019
import org.junit.jupiter.api.Test;
2120

2221
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -106,22 +105,6 @@ void functionWithVarargs() {
106105
evaluate("#varargsFunction2(9,'a',null,'b')", "9-[a, null, b]", String.class);
107106
}
108107

109-
@Disabled("Disabled until bugs are reported and fixed")
110-
@Test
111-
void functionWithVarargsViaMethodHandle_CurrentlyFailing() {
112-
// Calling 'public static String formatObjectVarargs(String format, Object... args)' -> String.format(format, args)
113-
114-
// No conversion necessary
115-
evaluate("#formatObjectVarargs('x -> %s', new Object[]{''})", "x -> ", String.class);
116-
evaluate("#formatObjectVarargs('x -> %s', new String[]{''})", "x -> ", String.class);
117-
evaluate("#formatObjectVarargs('x -> %s', new Object[]{' '})", "x -> ", String.class);
118-
evaluate("#formatObjectVarargs('x -> %s', new String[]{' '})", "x -> ", String.class);
119-
evaluate("#formatObjectVarargs('x -> %s', new Object[]{'a'})", "x -> a", String.class);
120-
evaluate("#formatObjectVarargs('x -> %s', new String[]{'a'})", "x -> a", String.class);
121-
evaluate("#formatObjectVarargs('x -> %s %s %s', new Object[]{'a', 'b', 'c'})", "x -> a b c", String.class);
122-
evaluate("#formatObjectVarargs('x -> %s %s %s', new String[]{'a', 'b', 'c'})", "x -> a b c", String.class);
123-
}
124-
125108
@Test // gh-33013
126109
void functionWithVarargsViaMethodHandle() {
127110
// Calling 'public static String formatObjectVarargs(String format, Object... args)' -> String.format(format, args)
@@ -138,6 +121,14 @@ void functionWithVarargsViaMethodHandle() {
138121
evaluate("#formatObjectVarargs('x -> %s', ' ')", "x -> ", String.class);
139122
evaluate("#formatObjectVarargs('x -> %s', 'a')", "x -> a", String.class);
140123
evaluate("#formatObjectVarargs('x -> %s %s %s', 'a', 'b', 'c')", "x -> a b c", String.class);
124+
evaluate("#formatObjectVarargs('x -> %s', new Object[]{''})", "x -> ", String.class);
125+
evaluate("#formatObjectVarargs('x -> %s', new String[]{''})", "x -> ", String.class);
126+
evaluate("#formatObjectVarargs('x -> %s', new Object[]{' '})", "x -> ", String.class);
127+
evaluate("#formatObjectVarargs('x -> %s', new String[]{' '})", "x -> ", String.class);
128+
evaluate("#formatObjectVarargs('x -> %s', new Object[]{'a'})", "x -> a", String.class);
129+
evaluate("#formatObjectVarargs('x -> %s', new String[]{'a'})", "x -> a", String.class);
130+
evaluate("#formatObjectVarargs('x -> %s %s %s', new Object[]{'a', 'b', 'c'})", "x -> a b c", String.class);
131+
evaluate("#formatObjectVarargs('x -> %s %s %s', new String[]{'a', 'b', 'c'})", "x -> a b c", String.class);
141132

142133
// Conversion necessary
143134
evaluate("#add('2', 5.0)", 7, Integer.class);

0 commit comments

Comments
 (0)