|
17 | 17 | package org.springframework.expression.spel.ast;
|
18 | 18 |
|
19 | 19 | import java.lang.reflect.Executable;
|
20 |
| -import java.util.Objects; |
| 20 | +import java.lang.reflect.Member; |
21 | 21 | import java.util.function.Supplier;
|
22 | 22 |
|
23 | 23 | import org.springframework.asm.MethodVisitor;
|
24 | 24 | import org.springframework.asm.Opcodes;
|
| 25 | +import org.springframework.asm.Type; |
25 | 26 | import org.springframework.expression.EvaluationException;
|
26 | 27 | import org.springframework.expression.TypedValue;
|
27 | 28 | import org.springframework.expression.common.ExpressionUtils;
|
|
32 | 33 | import org.springframework.expression.spel.SpelNode;
|
33 | 34 | import org.springframework.lang.Nullable;
|
34 | 35 | import org.springframework.util.Assert;
|
| 36 | +import org.springframework.util.ClassUtils; |
35 | 37 | import org.springframework.util.ObjectUtils;
|
| 38 | +import org.springframework.util.StringUtils; |
36 | 39 |
|
37 | 40 | /**
|
38 | 41 | * The common supertype of all AST nodes in a parsed Spring Expression Language
|
@@ -213,65 +216,95 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException
|
213 | 216 | * and if it is then the argument values will be appropriately packaged into an array.
|
214 | 217 | * @param mv the method visitor where code should be generated
|
215 | 218 | * @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 |
217 | 220 | * @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 |
218 | 246 | */
|
219 | 247 | 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); |
222 | 250 |
|
223 | 251 | if (executable.isVarArgs()) {
|
224 | 252 | // The final parameter may or may not need packaging into an array, or nothing may
|
225 | 253 | // 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; |
226 | 256 |
|
227 | 257 | // 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]); |
230 | 260 | }
|
231 | 261 |
|
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]; |
235 | 267 |
|
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]); |
250 | 272 | }
|
251 | 273 | 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 | + } |
254 | 288 | }
|
255 | 289 | }
|
256 | 290 | else {
|
257 |
| - for (int i = 0; i < paramCount; i++) { |
| 291 | + for (int i = 0; i < paramDescriptors.length;i++) { |
258 | 292 | cf.generateCodeForArgument(mv, arguments[i], paramDescriptors[i]);
|
259 | 293 | }
|
260 | 294 | }
|
261 | 295 | }
|
262 | 296 |
|
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; |
266 | 301 | }
|
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); |
275 | 308 | }
|
276 | 309 |
|
277 | 310 | /**
|
|
0 commit comments