Introduce generateCodeForArgument() in CodeFlow

Closes gh-32708
This commit is contained in:
Sam Brannen
2024-04-25 15:12:21 +03:00
parent 25fd5659cb
commit 0b5800ae39
3 changed files with 80 additions and 25 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}