From 28a3cd50aaa67d22d1d6f7e6630419ab600883d4 Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Sat, 25 Oct 2014 11:22:15 -0700 Subject: [PATCH] Resolved package tangle in spring-expression --- .../expression/spel/CodeFlow.java | 93 ++----------------- .../spel/ast/ConstructorReference.java | 2 +- .../spel/ast/FunctionReference.java | 2 +- .../expression/spel/ast/MethodReference.java | 2 +- .../expression/spel/ast/SpelNodeImpl.java | 89 ++++++++++++++++++ 5 files changed, 98 insertions(+), 90 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 8b98c97f39..2044e9e756 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 @@ -17,7 +17,6 @@ package org.springframework.expression.spel; import java.lang.reflect.Constructor; -import java.lang.reflect.Member; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -26,7 +25,6 @@ import java.util.Stack; import org.springframework.asm.ClassWriter; import org.springframework.asm.MethodVisitor; import org.springframework.asm.Opcodes; -import org.springframework.expression.spel.ast.SpelNodeImpl; import org.springframework.util.Assert; /** @@ -646,7 +644,12 @@ public class CodeFlow implements Opcodes { return toDescriptors(ctor.getParameterTypes()); } - private static String[] toDescriptors(Class[] types) { + /** + * Create an array of descriptors from an array of classes. + * @param types the input array of classes + * @return an array of descriptors + */ + public static String[] toDescriptors(Class[] types) { int typesCount = types.length; String[] descriptors = new String[typesCount]; for (int p = 0; p < typesCount; p++) { @@ -831,90 +834,6 @@ public class CodeFlow implements 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 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 arguments the expression nodes for the expression supplied argument values - */ - public static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Member member, SpelNodeImpl[] arguments) { - String[] paramDescriptors = null; - boolean isVarargs = false; - if (member instanceof Constructor) { - Constructor ctor = (Constructor)member; - paramDescriptors = toDescriptors(ctor.getParameterTypes()); - isVarargs = ctor.isVarArgs(); - } - else { // Method - Method method = (Method)member; - paramDescriptors = toDescriptors(method.getParameterTypes()); - isVarargs = method.isVarArgs(); - } - if (isVarargs) { - // The final parameter may or may not need packaging into an array, or nothing may - // have been passed to satisfy the varargs and so something needs to be built. - int p = 0; // Current supplied argument being processed - int childcount = arguments.length; - - // Fulfill all the parameter requirements except the last one - for (p = 0; p < paramDescriptors.length-1;p++) { - generateCodeForArgument(mv, cf, 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 && lastchild.getExitDescriptor().equals(arraytype)) { - generateCodeForArgument(mv, cf, lastchild, paramDescriptors[p]); - } - else { - arraytype = arraytype.substring(1); // trim the leading '[', may leave other '[' - // build array big enough to hold remaining arguments - CodeFlow.insertNewArrayCode(mv, childcount-p, arraytype); - // Package up the remaining arguments into the array - int arrayindex = 0; - while (p < childcount) { - SpelNodeImpl child = arguments[p]; - mv.visitInsn(DUP); - CodeFlow.insertOptimalLoad(mv, arrayindex++); - generateCodeForArgument(mv, cf, child, arraytype); - CodeFlow.insertArrayStore(mv, arraytype); - p++; - } - } - } - else { - for (int i = 0; i < paramDescriptors.length;i++) { - generateCodeForArgument(mv, cf, 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. - */ - public static void generateCodeForArgument(MethodVisitor mv, CodeFlow cf, SpelNodeImpl argument, String paramDescriptor) { - cf.enterCompilationScope(); - argument.generateCode(mv, cf); - boolean primitiveOnStack = CodeFlow.isPrimitive(cf.lastDescriptor()); - // Check if need to box it for the method reference? - if (primitiveOnStack && paramDescriptor.charAt(0) == 'L') { - CodeFlow.insertBoxIfNecessary(mv, cf.lastDescriptor().charAt(0)); - } - else if (paramDescriptor.length() == 1 && !primitiveOnStack) { - CodeFlow.insertUnboxInsns(mv, paramDescriptor.charAt(0), cf.lastDescriptor()); - } - else if (!cf.lastDescriptor().equals(paramDescriptor)) { - // This would be unnecessary in the case of subtyping (e.g. method takes Number but Integer passed in) - CodeFlow.insertCheckCast(mv, paramDescriptor); - } - cf.exitCompilationScope(); - } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java index 27319ad60a..44c301ebda 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java @@ -451,7 +451,7 @@ public class ConstructorReference extends SpelNodeImpl { // children[0] is the type of the constructor, don't want to include that in argument processing SpelNodeImpl[] arguments = new SpelNodeImpl[children.length-1]; System.arraycopy(children, 1, arguments, 0, children.length-1); - CodeFlow.generateCodeForArguments(mv, cf, constructor, arguments); + generateCodeForArguments(mv, cf, constructor, arguments); mv.visitMethodInsn(INVOKESPECIAL, classSlashedDescriptor, "", CodeFlow.createSignatureDescriptor(constructor), false); cf.pushDescriptor(this.exitTypeDescriptor); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java index 86150dcace..c876ed4bc1 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java @@ -180,7 +180,7 @@ public class FunctionReference extends SpelNodeImpl { @Override public void generateCode(MethodVisitor mv,CodeFlow cf) { String methodDeclaringClassSlashedDescriptor = this.method.getDeclaringClass().getName().replace('.', '/'); - CodeFlow.generateCodeForArguments(mv, cf, method, this.children); + generateCodeForArguments(mv, cf, method, this.children); mv.visitMethodInsn(INVOKESTATIC, methodDeclaringClassSlashedDescriptor, this.method.getName(), CodeFlow.createSignatureDescriptor(this.method), false); cf.pushDescriptor(this.exitTypeDescriptor); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java index addce1f6f3..98b9e6f85c 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java @@ -313,7 +313,7 @@ public class MethodReference extends SpelNodeImpl { CodeFlow.insertCheckCast(mv, "L"+ methodDeclaringClassSlashedDescriptor); } } - CodeFlow.generateCodeForArguments(mv, cf, method, children); + generateCodeForArguments(mv, cf, method, children); mv.visitMethodInsn(isStaticMethod ? INVOKESTATIC : INVOKEVIRTUAL, methodDeclaringClassSlashedDescriptor, method.getName(), CodeFlow.createSignatureDescriptor(method), itf); cf.pushDescriptor(this.exitTypeDescriptor); 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 d584a50fa6..ae8da2a1d8 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 @@ -16,6 +16,10 @@ package org.springframework.expression.spel.ast; +import java.lang.reflect.Constructor; +import java.lang.reflect.Member; +import java.lang.reflect.Method; + import org.springframework.asm.MethodVisitor; import org.springframework.asm.Opcodes; import org.springframework.expression.EvaluationException; @@ -208,4 +212,89 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes { public abstract TypedValue getValueInternal(ExpressionState expressionState) 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 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 arguments the expression nodes for the expression supplied argument values + */ + protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Member member, SpelNodeImpl[] arguments) { + String[] paramDescriptors = null; + boolean isVarargs = false; + if (member instanceof Constructor) { + Constructor ctor = (Constructor)member; + paramDescriptors = CodeFlow.toDescriptors(ctor.getParameterTypes()); + isVarargs = ctor.isVarArgs(); + } + else { // Method + Method method = (Method)member; + paramDescriptors = CodeFlow.toDescriptors(method.getParameterTypes()); + isVarargs = method.isVarArgs(); + } + if (isVarargs) { + // The final parameter may or may not need packaging into an array, or nothing may + // have been passed to satisfy the varargs and so something needs to be built. + int p = 0; // Current supplied argument being processed + int childcount = arguments.length; + + // Fulfill all the parameter requirements except the last one + for (p = 0; p < paramDescriptors.length-1;p++) { + generateCodeForArgument(mv, cf, 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 && lastchild.getExitDescriptor().equals(arraytype)) { + generateCodeForArgument(mv, cf, lastchild, paramDescriptors[p]); + } + else { + arraytype = arraytype.substring(1); // trim the leading '[', may leave other '[' + // build array big enough to hold remaining arguments + CodeFlow.insertNewArrayCode(mv, childcount-p, arraytype); + // Package up the remaining arguments into the array + int arrayindex = 0; + while (p < childcount) { + SpelNodeImpl child = arguments[p]; + mv.visitInsn(DUP); + CodeFlow.insertOptimalLoad(mv, arrayindex++); + generateCodeForArgument(mv, cf, child, arraytype); + CodeFlow.insertArrayStore(mv, arraytype); + p++; + } + } + } + else { + for (int i = 0; i < paramDescriptors.length;i++) { + generateCodeForArgument(mv, cf, 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. + */ + protected static void generateCodeForArgument(MethodVisitor mv, CodeFlow cf, SpelNodeImpl argument, String paramDescriptor) { + cf.enterCompilationScope(); + argument.generateCode(mv, cf); + boolean primitiveOnStack = CodeFlow.isPrimitive(cf.lastDescriptor()); + // Check if need to box it for the method reference? + if (primitiveOnStack && paramDescriptor.charAt(0) == 'L') { + CodeFlow.insertBoxIfNecessary(mv, cf.lastDescriptor().charAt(0)); + } + else if (paramDescriptor.length() == 1 && !primitiveOnStack) { + CodeFlow.insertUnboxInsns(mv, paramDescriptor.charAt(0), cf.lastDescriptor()); + } + else if (!cf.lastDescriptor().equals(paramDescriptor)) { + // This would be unnecessary in the case of subtyping (e.g. method takes Number but Integer passed in) + CodeFlow.insertCheckCast(mv, paramDescriptor); + } + cf.exitCompilationScope(); + } }