Skip to content

Commit

Permalink
Introduce generateCodeForArgument() in CodeFlow
Browse files Browse the repository at this point in the history
  • Loading branch information
sbrannen committed Apr 25, 2024
1 parent 25fd565 commit 0b5800a
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
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;
Expand Down Expand Up @@ -244,6 +245,74 @@ public String getClassName() {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -418,8 +418,7 @@ private void generateIndexCode(MethodVisitor mv, CodeFlow cf, SpelNodeImpl index
}

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,11 @@ protected ValueRef getValueRef(ExpressionState state) throws EvaluationException

/**
* 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) {
Expand All @@ -237,15 +237,15 @@ protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Me

// 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]);
String arrayType = paramDescriptors[paramDescriptors.length - 1];
// 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 '['
Expand All @@ -257,41 +257,28 @@ protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Me
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++;
}
}
}
else {
for (int i = 0; i < paramDescriptors.length;i++) {
generateCodeForArgument(mv, cf, arguments[i], paramDescriptors[i]);
cf.generateCodeForArgument(mv, arguments[i], paramDescriptors[i]);
}
}
}

/**
* 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);
}

}

0 comments on commit 0b5800a

Please sign in to comment.