Skip to content

Commit

Permalink
Support varargs-only MethodHandle as SpEL function
Browse files Browse the repository at this point in the history
Prior to this commit, if a MethodHandle was registered as a custom
function in the Spring Expression Language (SpEL) for a static method
that accepted only a variable argument list (for example,
`static String func(String... args)`), attempting to invoke the
registered function within a SpEL expression resulted in a
ClassCastException because the varargs array was unnecessarily wrapped
in an Object[].

This commit modifies the logic in FunctionReference's internal
executeFunctionViaMethodHandle() method to address that.

Closes gh-34109
  • Loading branch information
sbrannen committed Dec 18, 2024
1 parent 4b45338 commit 9c934b5
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,9 @@ else if (spelParamCount != declaredParamCount) {
ReflectionHelper.convertAllMethodHandleArguments(converter, functionArgs, methodHandle, varArgPosition);

if (isSuspectedVarargs) {
if (declaredParamCount == 1) {
// We only repackage the varargs if it is the ONLY argument -- for example,
if (declaredParamCount == 1 && !methodHandle.isVarargsCollector()) {
// We only repackage the arguments if the MethodHandle accepts a single
// argument AND the MethodHandle is not a "varargs collector" -- for example,
// when we are dealing with a bound MethodHandle.
functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(
methodHandle.type().parameterArray(), functionArgs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,16 @@ private static void populateMethodHandles(StandardEvaluationContext testContext)
"formatObjectVarargs", MethodType.methodType(String.class, String.class, Object[].class));
testContext.registerFunction("formatObjectVarargs", formatObjectVarargs);

// #formatObjectVarargs(format, args...)
// #formatPrimitiveVarargs(format, args...)
MethodHandle formatPrimitiveVarargs = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
"formatPrimitiveVarargs", MethodType.methodType(String.class, String.class, int[].class));
testContext.registerFunction("formatPrimitiveVarargs", formatPrimitiveVarargs);

// #varargsFunctionHandle(args...)
MethodHandle varargsFunctionHandle = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
"varargsFunction", MethodType.methodType(String.class, String[].class));
testContext.registerFunction("varargsFunctionHandle", varargsFunctionHandle);

// #add(int, int)
MethodHandle add = MethodHandles.lookup().findStatic(TestScenarioCreator.class,
"add", MethodType.methodType(int.class, int.class, int.class));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ void functionInvocationWithStringArgument() {

@Test
void functionWithVarargs() {
// static String varargsFunction(String... strings) -> Arrays.toString(strings)

evaluate("#varargsFunction()", "[]", String.class);
evaluate("#varargsFunction(new String[0])", "[]", String.class);
evaluate("#varargsFunction('a')", "[a]", String.class);
Expand Down Expand Up @@ -241,6 +243,27 @@ void functionFromMethodHandleWithListConvertedToVarargsArray() {
evaluate("#formatObjectVarargs('x -> %s %s %s', {'a', 'b', 'c'})", expected, String.class);
}

@Test // gh-34109
void functionViaMethodHandleForStaticMethodThatAcceptsOnlyVarargs() {
// #varargsFunctionHandle: static String varargsFunction(String... strings) -> Arrays.toString(strings)

evaluate("#varargsFunctionHandle()", "[]", String.class);
evaluate("#varargsFunctionHandle(new String[0])", "[]", String.class);
evaluate("#varargsFunctionHandle('a')", "[a]", String.class);
evaluate("#varargsFunctionHandle('a','b','c')", "[a, b, c]", String.class);
evaluate("#varargsFunctionHandle(new String[]{'a','b','c'})", "[a, b, c]", String.class);
// Conversion from int to String
evaluate("#varargsFunctionHandle(25)", "[25]", String.class);
evaluate("#varargsFunctionHandle('b',25)", "[b, 25]", String.class);
evaluate("#varargsFunctionHandle(new int[]{1, 2, 3})", "[1, 2, 3]", String.class);
// Strings that contain a comma
evaluate("#varargsFunctionHandle('a,b')", "[a,b]", String.class);
evaluate("#varargsFunctionHandle('a', 'x,y', 'd')", "[a, x,y, d]", String.class);
// null values
evaluate("#varargsFunctionHandle(null)", "[null]", String.class);
evaluate("#varargsFunctionHandle('a',null,'b')", "[a, null, b]", String.class);
}

@Test
void functionMethodMustBeStatic() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
Expand Down

0 comments on commit 9c934b5

Please sign in to comment.