Skip to content

Commit 8fe4493

Browse files
committed
Revise compilation support in SpEL for varargs array subtypes
This commit first reverts changes to SpelNodeImpl from the previous commit in order to reduce the scope of the overall change set. This commit then implements a different approach to support type-safe checks for array subtype compatibility. In order to support backward compatibility, this commit also reintroduces generateCodeForArguments(MethodVisitor, CodeFlow, Member, SpelNodeImpl[]) in deprecated form. See gh-32804
1 parent 12727a2 commit 8fe4493

File tree

2 files changed

+79
-43
lines changed

2 files changed

+79
-43
lines changed

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

Lines changed: 70 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@
1717
package org.springframework.expression.spel.ast;
1818

1919
import java.lang.reflect.Executable;
20-
import java.util.Objects;
20+
import java.lang.reflect.Member;
2121
import java.util.function.Supplier;
2222

2323
import org.springframework.asm.MethodVisitor;
2424
import org.springframework.asm.Opcodes;
25+
import org.springframework.asm.Type;
2526
import org.springframework.expression.EvaluationException;
2627
import org.springframework.expression.TypedValue;
2728
import org.springframework.expression.common.ExpressionUtils;
@@ -32,7 +33,9 @@
3233
import org.springframework.expression.spel.SpelNode;
3334
import org.springframework.lang.Nullable;
3435
import org.springframework.util.Assert;
36+
import org.springframework.util.ClassUtils;
3537
import org.springframework.util.ObjectUtils;
38+
import org.springframework.util.StringUtils;
3639

3740
/**
3841
* The common supertype of all AST nodes in a parsed Spring Expression Language
@@ -213,65 +216,95 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException
213216
* and if it is then the argument values will be appropriately packaged into an array.
214217
* @param mv the method visitor where code should be generated
215218
* @param cf the current codeflow
216-
* @param executable the method or constructor for which arguments are being set up
219+
* @param member the method or constructor for which arguments are being set up
217220
* @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[])}
223+
*/
224+
@Deprecated(since = "6.2")
225+
protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Member member, SpelNodeImpl[] arguments) {
226+
if (member instanceof Executable executable) {
227+
generateCodeForArguments(mv, cf, executable, arguments);
228+
}
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
218246
*/
219247
protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Executable executable, SpelNodeImpl[] arguments) {
220-
String[] paramDescriptors = CodeFlow.toDescriptors(executable.getParameterTypes());
221-
int paramCount = paramDescriptors.length;
248+
Class<?>[] parameterTypes = executable.getParameterTypes();
249+
String[] paramDescriptors = CodeFlow.toDescriptors(parameterTypes);
222250

223251
if (executable.isVarArgs()) {
224252
// The final parameter may or may not need packaging into an array, or nothing may
225253
// have been passed to satisfy the varargs and so something needs to be built.
254+
int p = 0; // Current supplied argument being processed
255+
int childCount = arguments.length;
226256

227257
// Fulfill all the parameter requirements except the last one
228-
for (int i = 0; i < paramCount - 1; i++) {
229-
cf.generateCodeForArgument(mv, arguments[i], paramDescriptors[i]);
258+
for (p = 0; p < paramDescriptors.length - 1; p++) {
259+
cf.generateCodeForArgument(mv, arguments[p], paramDescriptors[p]);
230260
}
231261

232-
int argCount = arguments.length;
233-
String varargsType = paramDescriptors[paramCount - 1];
234-
String varargsComponentType = varargsType.substring(1); // trim the leading '[', may leave other '['
262+
SpelNodeImpl lastChild = (childCount == 0 ? null : arguments[childCount - 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];
235267

236-
if (needsVarargsArrayWrapping(arguments, paramDescriptors)) {
237-
// Package up the remaining arguments into an array
238-
CodeFlow.insertNewArrayCode(mv, argCount - paramCount + 1, varargsComponentType);
239-
for (int argIndex = paramCount - 1, arrayIndex = 0; argIndex < argCount; argIndex++, arrayIndex++) {
240-
SpelNodeImpl child = arguments[argIndex];
241-
mv.visitInsn(DUP);
242-
CodeFlow.insertOptimalLoad(mv, arrayIndex);
243-
cf.generateCodeForArgument(mv, child, varargsComponentType);
244-
CodeFlow.insertArrayStore(mv, varargsComponentType);
245-
}
246-
}
247-
else if (varargsType.equals(arguments[argCount - 1].getExitDescriptor())) {
248-
// varargs type matches type of last argument
249-
cf.generateCodeForArgument(mv, arguments[argCount - 1], paramDescriptors[paramCount - 1]);
268+
// Determine if the final passed argument is already suitably packaged in array
269+
// form to be passed to the method
270+
if (lastChild != null && lastChildType != null && lastParameterType.isAssignableFrom(lastChildType)) {
271+
cf.generateCodeForArgument(mv, lastChild, paramDescriptors[p]);
250272
}
251273
else {
252-
// last argument is an array and varargs component type is a supertype of argument component type
253-
cf.generateCodeForArgument(mv, arguments[argCount - 1], varargsComponentType);
274+
String arrayType = paramDescriptors[paramDescriptors.length - 1];
275+
arrayType = arrayType.substring(1); // trim the leading '[', may leave other '['
276+
// build array big enough to hold remaining arguments
277+
CodeFlow.insertNewArrayCode(mv, childCount - p, arrayType);
278+
// Package up the remaining arguments into the array
279+
int arrayindex = 0;
280+
while (p < childCount) {
281+
SpelNodeImpl child = arguments[p];
282+
mv.visitInsn(DUP);
283+
CodeFlow.insertOptimalLoad(mv, arrayindex++);
284+
cf.generateCodeForArgument(mv, child, arrayType);
285+
CodeFlow.insertArrayStore(mv, arrayType);
286+
p++;
287+
}
254288
}
255289
}
256290
else {
257-
for (int i = 0; i < paramCount; i++) {
291+
for (int i = 0; i < paramDescriptors.length;i++) {
258292
cf.generateCodeForArgument(mv, arguments[i], paramDescriptors[i]);
259293
}
260294
}
261295
}
262296

263-
private static boolean needsVarargsArrayWrapping(SpelNodeImpl[] arguments, String[] paramDescriptors) {
264-
if (arguments.length == 0) {
265-
return true;
297+
@Nullable
298+
private static Class<?> loadClassForExitDescriptor(@Nullable String exitDescriptor, ClassLoader classLoader) {
299+
if (!StringUtils.hasText(exitDescriptor)) {
300+
return null;
266301
}
267-
268-
String lastExitTypeDescriptor = arguments[arguments.length - 1].exitTypeDescriptor;
269-
String lastParamDescriptor = paramDescriptors[paramDescriptors.length - 1];
270-
return countLeadingOpeningBrackets(Objects.requireNonNull(lastExitTypeDescriptor)) != countLeadingOpeningBrackets(lastParamDescriptor);
271-
}
272-
273-
private static long countLeadingOpeningBrackets(String lastExitTypeDescriptor) {
274-
return lastExitTypeDescriptor.chars().takeWhile(c -> c == '[').count();
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);
275308
}
276309

277310
/**

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4920,19 +4920,22 @@ void constructorReference() {
49204920
// varargs
49214921
expression = parser.parseExpression("new " + testclass8 + "(#root)");
49224922
Object[] objectArray = { "a", "b", "c" };
4923-
assertThat(expression.getValue(objectArray).getClass().getName()).isEqualTo(testclass8);
4923+
o = expression.getValue(objectArray);
4924+
assertThat(o).isExactlyInstanceOf(TestClass8.class);
49244925
assertCanCompile(expression);
49254926
o = expression.getValue(objectArray);
4926-
assertThat(o.getClass().getName()).isEqualTo(testclass8);
4927+
assertThat(o).isExactlyInstanceOf(TestClass8.class);
49274928
tc8 = (TestClass8) o;
49284929
assertThat(tc8.args).containsExactly("a", "b", "c");
49294930

49304931
// varargs with argument component type that is a subtype of the varargs component type.
49314932
expression = parser.parseExpression("new " + testclass8 + "(#root)");
4932-
assertThat(expression.getValue(objectArray).getClass().getName()).isEqualTo(testclass8);
4933+
String[] stringArray = { "a", "b", "c" };
4934+
o = expression.getValue(stringArray);
4935+
assertThat(o).isExactlyInstanceOf(TestClass8.class);
49334936
assertCanCompile(expression);
4934-
o = expression.getValue(new String[] { "a", "b", "c" });
4935-
assertThat(o.getClass().getName()).isEqualTo(testclass8);
4937+
o = expression.getValue(stringArray);
4938+
assertThat(o).isExactlyInstanceOf(TestClass8.class);
49364939
tc8 = (TestClass8) o;
49374940
assertThat(tc8.args).containsExactly("a", "b", "c");
49384941

@@ -6939,7 +6942,7 @@ public void eighteen(String a, Object... vargs) {
69396942
s = a + "::";
69406943
}
69416944
else {
6942-
s = a+"::";
6945+
s = a + "::";
69436946
for (Object varg: vargs) {
69446947
s += varg;
69456948
}

0 commit comments

Comments
 (0)