Introduce generateCodeForArgument() in CodeFlow
Closes gh-32708
This commit is contained in:
@@ -30,6 +30,7 @@ import org.springframework.asm.MethodVisitor;
|
||||
import org.springframework.asm.Opcodes;
|
||||
import org.springframework.lang.Contract;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
@@ -244,6 +245,74 @@ public class CodeFlow implements Opcodes {
|
||||
return this.className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate bytecode that loads the supplied argument onto the stack.
|
||||
* <p>Delegates to {@link #generateCodeForArgument(MethodVisitor, SpelNode, String)}
|
||||
* with the {@linkplain #toDescriptor(Class) descriptor} for
|
||||
* the supplied {@code requiredType}.
|
||||
* <p>This method also performs any boxing, unboxing, or check-casting
|
||||
* necessary to ensure that the type of the argument on the stack matches the
|
||||
* supplied {@code requiredType}.
|
||||
* <p>Use this method when a node in the AST will be used as an argument for
|
||||
* a constructor or method invocation. For example, if you wish to invoke a
|
||||
* method with an {@code indexNode} that must be of type {@code int} for the
|
||||
* actual method invocation within bytecode, you would call
|
||||
* {@code codeFlow.generateCodeForArgument(methodVisitor, indexNode, int.class)}.
|
||||
* @param methodVisitor the ASM {@link MethodVisitor} into which code should
|
||||
* be generated
|
||||
* @param argument a {@link SpelNode} that represents an argument to a method
|
||||
* or constructor
|
||||
* @param requiredType the required type for the argument when invoking the
|
||||
* corresponding constructor or method
|
||||
* @since 6.2
|
||||
* @see #generateCodeForArgument(MethodVisitor, SpelNode, String)
|
||||
*/
|
||||
public void generateCodeForArgument(MethodVisitor methodVisitor, SpelNode argument, Class<?> requiredType) {
|
||||
generateCodeForArgument(methodVisitor, argument, toDescriptor(requiredType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate bytecode that loads the supplied argument onto the stack.
|
||||
* <p>This method also performs any boxing, unboxing, or check-casting
|
||||
* necessary to ensure that the type of the argument on the stack matches the
|
||||
* supplied {@code requiredTypeDesc}.
|
||||
* <p>Use this method when a node in the AST will be used as an argument for
|
||||
* a constructor or method invocation. For example, if you wish to invoke a
|
||||
* method with an {@code indexNode} that must be of type {@code int} for the
|
||||
* actual method invocation within bytecode, you would call
|
||||
* {@code codeFlow.generateCodeForArgument(methodVisitor, indexNode, "I")}.
|
||||
* @param methodVisitor the ASM {@link MethodVisitor} into which code should
|
||||
* be generated
|
||||
* @param argument a {@link SpelNode} that represents an argument to a method
|
||||
* or constructor
|
||||
* @param requiredTypeDesc a descriptor for the required type for the argument
|
||||
* when invoking the corresponding constructor or method
|
||||
* @since 6.2
|
||||
* @see #generateCodeForArgument(MethodVisitor, SpelNode, Class)
|
||||
* @see #toDescriptor(Class)
|
||||
*/
|
||||
public void generateCodeForArgument(MethodVisitor methodVisitor, SpelNode argument, String requiredTypeDesc) {
|
||||
enterCompilationScope();
|
||||
argument.generateCode(methodVisitor, this);
|
||||
String lastDesc = lastDescriptor();
|
||||
Assert.state(lastDesc != null, "No last descriptor");
|
||||
boolean primitiveOnStack = isPrimitive(lastDesc);
|
||||
// Check if we need to box it.
|
||||
if (primitiveOnStack && requiredTypeDesc.charAt(0) == 'L') {
|
||||
insertBoxIfNecessary(methodVisitor, lastDesc.charAt(0));
|
||||
}
|
||||
// Check if we need to unbox it.
|
||||
else if (requiredTypeDesc.length() == 1 && !primitiveOnStack) {
|
||||
insertUnboxInsns(methodVisitor, requiredTypeDesc.charAt(0), lastDesc);
|
||||
}
|
||||
// Check if we need to check-cast
|
||||
else if (!requiredTypeDesc.equals(lastDesc)) {
|
||||
// This would be unnecessary in the case of subtyping (e.g. method takes Number but Integer passed in)
|
||||
insertCheckCast(methodVisitor, requiredTypeDesc);
|
||||
}
|
||||
exitCompilationScope();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Insert any necessary cast and value call to convert from a boxed type to a
|
||||
|
||||
@@ -418,8 +418,7 @@ public class Indexer extends SpelNodeImpl {
|
||||
}
|
||||
|
||||
private void generateIndexCode(MethodVisitor mv, CodeFlow cf, SpelNodeImpl indexNode, Class<?> indexType) {
|
||||
String indexDesc = CodeFlow.toDescriptor(indexType);
|
||||
generateCodeForArgument(mv, cf, indexNode, indexDesc);
|
||||
cf.generateCodeForArgument(mv, indexNode, indexType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -210,11 +210,11 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
|
||||
|
||||
/**
|
||||
* Generate code that handles building the argument values for the specified method.
|
||||
* This method will take account of whether the invoked method is a varargs method
|
||||
* <p>This method will take into account whether the invoked method is a varargs method,
|
||||
* and if it is then the argument values will be appropriately packaged into an array.
|
||||
* @param mv the method visitor where code should be generated
|
||||
* @param cf the current codeflow
|
||||
* @param member the method or constructor for which arguments are being setup
|
||||
* @param member the method or constructor for which arguments are being set up
|
||||
* @param arguments the expression nodes for the expression supplied argument values
|
||||
*/
|
||||
protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Member member, SpelNodeImpl[] arguments) {
|
||||
@@ -237,7 +237,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
|
||||
|
||||
// Fulfill all the parameter requirements except the last one
|
||||
for (p = 0; p < paramDescriptors.length - 1; p++) {
|
||||
generateCodeForArgument(mv, cf, arguments[p], paramDescriptors[p]);
|
||||
cf.generateCodeForArgument(mv, arguments[p], paramDescriptors[p]);
|
||||
}
|
||||
|
||||
SpelNodeImpl lastChild = (childCount == 0 ? null : arguments[childCount - 1]);
|
||||
@@ -245,7 +245,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
|
||||
// Determine if the final passed argument is already suitably packaged in array
|
||||
// form to be passed to the method
|
||||
if (lastChild != null && arrayType.equals(lastChild.getExitDescriptor())) {
|
||||
generateCodeForArgument(mv, cf, lastChild, paramDescriptors[p]);
|
||||
cf.generateCodeForArgument(mv, lastChild, paramDescriptors[p]);
|
||||
}
|
||||
else {
|
||||
arrayType = arrayType.substring(1); // trim the leading '[', may leave other '['
|
||||
@@ -257,7 +257,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
|
||||
SpelNodeImpl child = arguments[p];
|
||||
mv.visitInsn(DUP);
|
||||
CodeFlow.insertOptimalLoad(mv, arrayindex++);
|
||||
generateCodeForArgument(mv, cf, child, arrayType);
|
||||
cf.generateCodeForArgument(mv, child, arrayType);
|
||||
CodeFlow.insertArrayStore(mv, arrayType);
|
||||
p++;
|
||||
}
|
||||
@@ -265,7 +265,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < paramDescriptors.length;i++) {
|
||||
generateCodeForArgument(mv, cf, arguments[i], paramDescriptors[i]);
|
||||
cf.generateCodeForArgument(mv, arguments[i], paramDescriptors[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,25 +273,12 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
|
||||
/**
|
||||
* Ask an argument to generate its bytecode and then follow it up
|
||||
* with any boxing/unboxing/checkcasting to ensure it matches the expected parameter descriptor.
|
||||
* @deprecated As of Spring Framework 6.2, in favor of
|
||||
* {@link CodeFlow#generateCodeForArgument(MethodVisitor, SpelNode, String)}
|
||||
*/
|
||||
@Deprecated(since = "6.2")
|
||||
protected static void generateCodeForArgument(MethodVisitor mv, CodeFlow cf, SpelNodeImpl argument, String paramDesc) {
|
||||
cf.enterCompilationScope();
|
||||
argument.generateCode(mv, cf);
|
||||
String lastDesc = cf.lastDescriptor();
|
||||
Assert.state(lastDesc != null, "No last descriptor");
|
||||
boolean primitiveOnStack = CodeFlow.isPrimitive(lastDesc);
|
||||
// Check if need to box it for the method reference?
|
||||
if (primitiveOnStack && paramDesc.charAt(0) == 'L') {
|
||||
CodeFlow.insertBoxIfNecessary(mv, lastDesc.charAt(0));
|
||||
}
|
||||
else if (paramDesc.length() == 1 && !primitiveOnStack) {
|
||||
CodeFlow.insertUnboxInsns(mv, paramDesc.charAt(0), lastDesc);
|
||||
}
|
||||
else if (!paramDesc.equals(lastDesc)) {
|
||||
// This would be unnecessary in the case of subtyping (e.g. method takes Number but Integer passed in)
|
||||
CodeFlow.insertCheckCast(mv, paramDesc);
|
||||
}
|
||||
cf.exitCompilationScope();
|
||||
cf.generateCodeForArgument(mv, argument, paramDesc);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user