Skip to content

Commit 0b5800a

Browse files
committed
Introduce generateCodeForArgument() in CodeFlow
Closes gh-32708
1 parent 25fd565 commit 0b5800a

File tree

3 files changed

+80
-25
lines changed

3 files changed

+80
-25
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java

+69
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.asm.Opcodes;
3131
import org.springframework.lang.Contract;
3232
import org.springframework.lang.Nullable;
33+
import org.springframework.util.Assert;
3334
import org.springframework.util.ClassUtils;
3435
import org.springframework.util.CollectionUtils;
3536
import org.springframework.util.ConcurrentReferenceHashMap;
@@ -244,6 +245,74 @@ public String getClassName() {
244245
return this.className;
245246
}
246247

248+
/**
249+
* Generate bytecode that loads the supplied argument onto the stack.
250+
* <p>Delegates to {@link #generateCodeForArgument(MethodVisitor, SpelNode, String)}
251+
* with the {@linkplain #toDescriptor(Class) descriptor} for
252+
* the supplied {@code requiredType}.
253+
* <p>This method also performs any boxing, unboxing, or check-casting
254+
* necessary to ensure that the type of the argument on the stack matches the
255+
* supplied {@code requiredType}.
256+
* <p>Use this method when a node in the AST will be used as an argument for
257+
* a constructor or method invocation. For example, if you wish to invoke a
258+
* method with an {@code indexNode} that must be of type {@code int} for the
259+
* actual method invocation within bytecode, you would call
260+
* {@code codeFlow.generateCodeForArgument(methodVisitor, indexNode, int.class)}.
261+
* @param methodVisitor the ASM {@link MethodVisitor} into which code should
262+
* be generated
263+
* @param argument a {@link SpelNode} that represents an argument to a method
264+
* or constructor
265+
* @param requiredType the required type for the argument when invoking the
266+
* corresponding constructor or method
267+
* @since 6.2
268+
* @see #generateCodeForArgument(MethodVisitor, SpelNode, String)
269+
*/
270+
public void generateCodeForArgument(MethodVisitor methodVisitor, SpelNode argument, Class<?> requiredType) {
271+
generateCodeForArgument(methodVisitor, argument, toDescriptor(requiredType));
272+
}
273+
274+
/**
275+
* Generate bytecode that loads the supplied argument onto the stack.
276+
* <p>This method also performs any boxing, unboxing, or check-casting
277+
* necessary to ensure that the type of the argument on the stack matches the
278+
* supplied {@code requiredTypeDesc}.
279+
* <p>Use this method when a node in the AST will be used as an argument for
280+
* a constructor or method invocation. For example, if you wish to invoke a
281+
* method with an {@code indexNode} that must be of type {@code int} for the
282+
* actual method invocation within bytecode, you would call
283+
* {@code codeFlow.generateCodeForArgument(methodVisitor, indexNode, "I")}.
284+
* @param methodVisitor the ASM {@link MethodVisitor} into which code should
285+
* be generated
286+
* @param argument a {@link SpelNode} that represents an argument to a method
287+
* or constructor
288+
* @param requiredTypeDesc a descriptor for the required type for the argument
289+
* when invoking the corresponding constructor or method
290+
* @since 6.2
291+
* @see #generateCodeForArgument(MethodVisitor, SpelNode, Class)
292+
* @see #toDescriptor(Class)
293+
*/
294+
public void generateCodeForArgument(MethodVisitor methodVisitor, SpelNode argument, String requiredTypeDesc) {
295+
enterCompilationScope();
296+
argument.generateCode(methodVisitor, this);
297+
String lastDesc = lastDescriptor();
298+
Assert.state(lastDesc != null, "No last descriptor");
299+
boolean primitiveOnStack = isPrimitive(lastDesc);
300+
// Check if we need to box it.
301+
if (primitiveOnStack && requiredTypeDesc.charAt(0) == 'L') {
302+
insertBoxIfNecessary(methodVisitor, lastDesc.charAt(0));
303+
}
304+
// Check if we need to unbox it.
305+
else if (requiredTypeDesc.length() == 1 && !primitiveOnStack) {
306+
insertUnboxInsns(methodVisitor, requiredTypeDesc.charAt(0), lastDesc);
307+
}
308+
// Check if we need to check-cast
309+
else if (!requiredTypeDesc.equals(lastDesc)) {
310+
// This would be unnecessary in the case of subtyping (e.g. method takes Number but Integer passed in)
311+
insertCheckCast(methodVisitor, requiredTypeDesc);
312+
}
313+
exitCompilationScope();
314+
}
315+
247316

248317
/**
249318
* Insert any necessary cast and value call to convert from a boxed type to a

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -418,8 +418,7 @@ private void generateIndexCode(MethodVisitor mv, CodeFlow cf, SpelNodeImpl index
418418
}
419419

420420
private void generateIndexCode(MethodVisitor mv, CodeFlow cf, SpelNodeImpl indexNode, Class<?> indexType) {
421-
String indexDesc = CodeFlow.toDescriptor(indexType);
422-
generateCodeForArgument(mv, cf, indexNode, indexDesc);
421+
cf.generateCodeForArgument(mv, indexNode, indexType);
423422
}
424423

425424
@Override

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

+10-23
Original file line numberDiff line numberDiff line change
@@ -210,11 +210,11 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException
210210

211211
/**
212212
* Generate code that handles building the argument values for the specified method.
213-
* This method will take account of whether the invoked method is a varargs method
213+
* <p>This method will take into account whether the invoked method is a varargs method,
214214
* and if it is then the argument values will be appropriately packaged into an array.
215215
* @param mv the method visitor where code should be generated
216216
* @param cf the current codeflow
217-
* @param member the method or constructor for which arguments are being setup
217+
* @param member the method or constructor for which arguments are being set up
218218
* @param arguments the expression nodes for the expression supplied argument values
219219
*/
220220
protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Member member, SpelNodeImpl[] arguments) {
@@ -237,15 +237,15 @@ protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Me
237237

238238
// Fulfill all the parameter requirements except the last one
239239
for (p = 0; p < paramDescriptors.length - 1; p++) {
240-
generateCodeForArgument(mv, cf, arguments[p], paramDescriptors[p]);
240+
cf.generateCodeForArgument(mv, arguments[p], paramDescriptors[p]);
241241
}
242242

243243
SpelNodeImpl lastChild = (childCount == 0 ? null : arguments[childCount - 1]);
244244
String arrayType = paramDescriptors[paramDescriptors.length - 1];
245245
// Determine if the final passed argument is already suitably packaged in array
246246
// form to be passed to the method
247247
if (lastChild != null && arrayType.equals(lastChild.getExitDescriptor())) {
248-
generateCodeForArgument(mv, cf, lastChild, paramDescriptors[p]);
248+
cf.generateCodeForArgument(mv, lastChild, paramDescriptors[p]);
249249
}
250250
else {
251251
arrayType = arrayType.substring(1); // trim the leading '[', may leave other '['
@@ -257,41 +257,28 @@ protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Me
257257
SpelNodeImpl child = arguments[p];
258258
mv.visitInsn(DUP);
259259
CodeFlow.insertOptimalLoad(mv, arrayindex++);
260-
generateCodeForArgument(mv, cf, child, arrayType);
260+
cf.generateCodeForArgument(mv, child, arrayType);
261261
CodeFlow.insertArrayStore(mv, arrayType);
262262
p++;
263263
}
264264
}
265265
}
266266
else {
267267
for (int i = 0; i < paramDescriptors.length;i++) {
268-
generateCodeForArgument(mv, cf, arguments[i], paramDescriptors[i]);
268+
cf.generateCodeForArgument(mv, arguments[i], paramDescriptors[i]);
269269
}
270270
}
271271
}
272272

273273
/**
274274
* Ask an argument to generate its bytecode and then follow it up
275275
* with any boxing/unboxing/checkcasting to ensure it matches the expected parameter descriptor.
276+
* @deprecated As of Spring Framework 6.2, in favor of
277+
* {@link CodeFlow#generateCodeForArgument(MethodVisitor, SpelNode, String)}
276278
*/
279+
@Deprecated(since = "6.2")
277280
protected static void generateCodeForArgument(MethodVisitor mv, CodeFlow cf, SpelNodeImpl argument, String paramDesc) {
278-
cf.enterCompilationScope();
279-
argument.generateCode(mv, cf);
280-
String lastDesc = cf.lastDescriptor();
281-
Assert.state(lastDesc != null, "No last descriptor");
282-
boolean primitiveOnStack = CodeFlow.isPrimitive(lastDesc);
283-
// Check if need to box it for the method reference?
284-
if (primitiveOnStack && paramDesc.charAt(0) == 'L') {
285-
CodeFlow.insertBoxIfNecessary(mv, lastDesc.charAt(0));
286-
}
287-
else if (paramDesc.length() == 1 && !primitiveOnStack) {
288-
CodeFlow.insertUnboxInsns(mv, paramDesc.charAt(0), lastDesc);
289-
}
290-
else if (!paramDesc.equals(lastDesc)) {
291-
// This would be unnecessary in the case of subtyping (e.g. method takes Number but Integer passed in)
292-
CodeFlow.insertCheckCast(mv, paramDesc);
293-
}
294-
cf.exitCompilationScope();
281+
cf.generateCodeForArgument(mv, argument, paramDesc);
295282
}
296283

297284
}

0 commit comments

Comments
 (0)