Skip to content

Commit 061c13d

Browse files
committed
Support compilation of varargs invocations in SpEL for array subtypes
This commit merges support for compiling SpEL expressions that contain varargs invocations where the supplied array is a subtype of the declared varargs array type. Closes gh-32804
2 parents 9516f87 + 8fe4493 commit 061c13d

File tree

2 files changed

+196
-69
lines changed

2 files changed

+196
-69
lines changed

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

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616

1717
package org.springframework.expression.spel.ast;
1818

19-
import java.lang.reflect.Constructor;
19+
import java.lang.reflect.Executable;
2020
import java.lang.reflect.Member;
21-
import java.lang.reflect.Method;
2221
import java.util.function.Supplier;
2322

2423
import org.springframework.asm.MethodVisitor;
2524
import org.springframework.asm.Opcodes;
25+
import org.springframework.asm.Type;
2626
import org.springframework.expression.EvaluationException;
2727
import org.springframework.expression.TypedValue;
2828
import org.springframework.expression.common.ExpressionUtils;
@@ -33,7 +33,9 @@
3333
import org.springframework.expression.spel.SpelNode;
3434
import org.springframework.lang.Nullable;
3535
import org.springframework.util.Assert;
36+
import org.springframework.util.ClassUtils;
3637
import org.springframework.util.ObjectUtils;
38+
import org.springframework.util.StringUtils;
3739

3840
/**
3941
* The common supertype of all AST nodes in a parsed Spring Expression Language
@@ -216,20 +218,37 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException
216218
* @param cf the current codeflow
217219
* @param member the method or constructor for which arguments are being set up
218220
* @param arguments the expression nodes for the expression supplied argument values
221+
* @deprecated As of Spring Framework 6.2, in favor of
222+
* {@link #generateCodeForArguments(MethodVisitor, CodeFlow, Executable, SpelNodeImpl[])}
219223
*/
224+
@Deprecated(since = "6.2")
220225
protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Member member, SpelNodeImpl[] arguments) {
221-
String[] paramDescriptors = null;
222-
boolean isVarargs = false;
223-
if (member instanceof Constructor<?> ctor) {
224-
paramDescriptors = CodeFlow.toDescriptors(ctor.getParameterTypes());
225-
isVarargs = ctor.isVarArgs();
226+
if (member instanceof Executable executable) {
227+
generateCodeForArguments(mv, cf, executable, arguments);
226228
}
227-
else { // Method
228-
Method method = (Method)member;
229-
paramDescriptors = CodeFlow.toDescriptors(method.getParameterTypes());
230-
isVarargs = method.isVarArgs();
231-
}
232-
if (isVarargs) {
229+
throw new IllegalArgumentException(
230+
"The supplied member must be an instance of java.lang.reflect.Executable: " + member);
231+
}
232+
233+
/**
234+
* Generate code that handles building the argument values for the specified
235+
* {@link Executable} (method or constructor).
236+
* <p>This method takes into account whether the invoked executable was
237+
* declared to accept varargs, and if it was then the argument values will be
238+
* appropriately packaged into an array.
239+
* @param mv the method visitor where code should be generated
240+
* @param cf the current {@link CodeFlow}
241+
* @param executable the {@link Executable} (method or constructor) for which
242+
* arguments are being set up
243+
* @param arguments the expression nodes for the expression supplied argument
244+
* values
245+
* @since 6.2
246+
*/
247+
protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Executable executable, SpelNodeImpl[] arguments) {
248+
Class<?>[] parameterTypes = executable.getParameterTypes();
249+
String[] paramDescriptors = CodeFlow.toDescriptors(parameterTypes);
250+
251+
if (executable.isVarArgs()) {
233252
// The final parameter may or may not need packaging into an array, or nothing may
234253
// have been passed to satisfy the varargs and so something needs to be built.
235254
int p = 0; // Current supplied argument being processed
@@ -241,13 +260,18 @@ protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Me
241260
}
242261

243262
SpelNodeImpl lastChild = (childCount == 0 ? null : arguments[childCount - 1]);
244-
String arrayType = paramDescriptors[paramDescriptors.length - 1];
263+
ClassLoader classLoader = executable.getDeclaringClass().getClassLoader();
264+
Class<?> lastChildType = (lastChild != null ?
265+
loadClassForExitDescriptor(lastChild.getExitDescriptor(), classLoader) : null);
266+
Class<?> lastParameterType = parameterTypes[parameterTypes.length - 1];
267+
245268
// Determine if the final passed argument is already suitably packaged in array
246269
// form to be passed to the method
247-
if (lastChild != null && arrayType.equals(lastChild.getExitDescriptor())) {
270+
if (lastChild != null && lastChildType != null && lastParameterType.isAssignableFrom(lastChildType)) {
248271
cf.generateCodeForArgument(mv, lastChild, paramDescriptors[p]);
249272
}
250273
else {
274+
String arrayType = paramDescriptors[paramDescriptors.length - 1];
251275
arrayType = arrayType.substring(1); // trim the leading '[', may leave other '['
252276
// build array big enough to hold remaining arguments
253277
CodeFlow.insertNewArrayCode(mv, childCount - p, arrayType);
@@ -270,6 +294,19 @@ protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Me
270294
}
271295
}
272296

297+
@Nullable
298+
private static Class<?> loadClassForExitDescriptor(@Nullable String exitDescriptor, ClassLoader classLoader) {
299+
if (!StringUtils.hasText(exitDescriptor)) {
300+
return null;
301+
}
302+
String typeDescriptor = exitDescriptor;
303+
if (typeDescriptor.startsWith("[") || typeDescriptor.startsWith("L")) {
304+
typeDescriptor += ";";
305+
}
306+
String className = Type.getType(typeDescriptor).getClassName();
307+
return ClassUtils.resolveClassName(className, classLoader);
308+
}
309+
273310
/**
274311
* Ask an argument to generate its bytecode and then follow it up
275312
* with any boxing/unboxing/checkcasting to ensure it matches the expected parameter descriptor.

0 commit comments

Comments
 (0)