From 0b5800ae39f4d6e1731c0ca57c2bef6bc960f8a8 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:12:21 +0300 Subject: [PATCH] Introduce generateCodeForArgument() in CodeFlow Closes gh-32708 --- .../expression/spel/CodeFlow.java | 69 +++++++++++++++++++ .../expression/spel/ast/Indexer.java | 3 +- .../expression/spel/ast/SpelNodeImpl.java | 33 +++------ 3 files changed, 80 insertions(+), 25 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java b/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java index 9437835e7244..618cbda2a415 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java @@ -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; @@ -244,6 +245,74 @@ public String getClassName() { return this.className; } + /** + * Generate bytecode that loads the supplied argument onto the stack. + *

Delegates to {@link #generateCodeForArgument(MethodVisitor, SpelNode, String)} + * with the {@linkplain #toDescriptor(Class) descriptor} for + * the supplied {@code requiredType}. + *

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}. + *

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. + *

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}. + *

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 diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index c6f237cb021c..be4b91d7185b 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -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 diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java index a46a130f0a85..06499fa14c44 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java @@ -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 + *

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 @@ 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]); @@ -245,7 +245,7 @@ protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Me // 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 @@ 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++; } @@ -265,7 +265,7 @@ protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Me } 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 @@ protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Me /** * 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); } }