Browse Source

Polishing

pull/32829/head
Sam Brannen 2 years ago
parent
commit
fc07946e60
  1. 19
      spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java
  2. 84
      spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java
  3. 70
      spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java

19
spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java

@ -957,7 +957,7 @@ public class CodeFlow implements Opcodes {
*/ */
public static void insertOptimalLoad(MethodVisitor mv, int value) { public static void insertOptimalLoad(MethodVisitor mv, int value) {
if (value < 6) { if (value < 6) {
mv.visitInsn(ICONST_0+value); mv.visitInsn(ICONST_0 + value);
} }
else if (value < Byte.MAX_VALUE) { else if (value < Byte.MAX_VALUE) {
mv.visitIntInsn(BIPUSH, value); mv.visitIntInsn(BIPUSH, value);
@ -971,15 +971,16 @@ public class CodeFlow implements Opcodes {
} }
/** /**
* Produce appropriate bytecode to store a stack item in an array. The * Produce appropriate bytecode to store a stack item in an array.
* instruction to use varies depending on whether the type * <p>The instruction to use varies depending on whether the type is a
* is a primitive or reference type. * primitive or reference type.
* @param mv where to insert the bytecode * @param mv where to insert the bytecode
* @param arrayElementType the type of the array elements * @param arrayComponentType the component type of the array
*/ */
public static void insertArrayStore(MethodVisitor mv, String arrayElementType) { public static void insertArrayStore(MethodVisitor mv, String arrayComponentType) {
if (arrayElementType.length() == 1) { if (arrayComponentType.length() == 1) {
switch (arrayElementType.charAt(0)) { char componentType = arrayComponentType.charAt(0);
switch (componentType) {
case 'B', 'Z' -> mv.visitInsn(BASTORE); case 'B', 'Z' -> mv.visitInsn(BASTORE);
case 'I' -> mv.visitInsn(IASTORE); case 'I' -> mv.visitInsn(IASTORE);
case 'J' -> mv.visitInsn(LASTORE); case 'J' -> mv.visitInsn(LASTORE);
@ -987,7 +988,7 @@ public class CodeFlow implements Opcodes {
case 'D' -> mv.visitInsn(DASTORE); case 'D' -> mv.visitInsn(DASTORE);
case 'C' -> mv.visitInsn(CASTORE); case 'C' -> mv.visitInsn(CASTORE);
case 'S' -> mv.visitInsn(SASTORE); case 'S' -> mv.visitInsn(SASTORE);
default -> throw new IllegalArgumentException("Unexpected array type " + arrayElementType.charAt(0)); default -> throw new IllegalArgumentException("Unexpected array component type " + componentType);
} }
} }
else { else {

84
spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java

@ -211,18 +211,22 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
/** /**
* Generate code that handles building the argument values for the specified method. * Generate code that handles building the argument values for the specified
* <p>This method will take into account whether the invoked method is a varargs method, * {@link Member} (method or constructor).
* and if it is then the argument values will be appropriately packaged into an array. * <p>This method takes into account whether the method or constructor was
* declared to accept varargs, and if it was then the argument values will be
* appropriately packaged into an array.
* @param mv the method visitor where code should be generated * @param mv the method visitor where code should be generated
* @param cf the current codeflow * @param cf the current {@link CodeFlow}
* @param member the method or constructor for which arguments are being set up * @param member the method or constructor for which arguments are being set up
* @param arguments the expression nodes for the expression supplied argument values * @param arguments the expression nodes for the expression supplied argument values
* @deprecated As of Spring Framework 6.2, in favor of * @deprecated As of Spring Framework 6.2, in favor of
* {@link #generateCodeForArguments(MethodVisitor, CodeFlow, Executable, SpelNodeImpl[])} * {@link #generateCodeForArguments(MethodVisitor, CodeFlow, Executable, SpelNodeImpl[])}
*/ */
@Deprecated(since = "6.2") @Deprecated(since = "6.2")
protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Member member, SpelNodeImpl[] arguments) { protected static void generateCodeForArguments(
MethodVisitor mv, CodeFlow cf, Member member, SpelNodeImpl[] arguments) {
if (member instanceof Executable executable) { if (member instanceof Executable executable) {
generateCodeForArguments(mv, cf, executable, arguments); generateCodeForArguments(mv, cf, executable, arguments);
} }
@ -233,7 +237,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
/** /**
* Generate code that handles building the argument values for the specified * Generate code that handles building the argument values for the specified
* {@link Executable} (method or constructor). * {@link Executable} (method or constructor).
* <p>This method takes into account whether the invoked executable was * <p>This method takes into account whether the method or constructor was
* declared to accept varargs, and if it was then the argument values will be * declared to accept varargs, and if it was then the argument values will be
* appropriately packaged into an array. * appropriately packaged into an array.
* @param mv the method visitor where code should be generated * @param mv the method visitor where code should be generated
@ -244,52 +248,56 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
* values * values
* @since 6.2 * @since 6.2
*/ */
protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Executable executable, SpelNodeImpl[] arguments) { protected static void generateCodeForArguments(
MethodVisitor mv, CodeFlow cf, Executable executable, SpelNodeImpl[] arguments) {
Class<?>[] parameterTypes = executable.getParameterTypes(); Class<?>[] parameterTypes = executable.getParameterTypes();
String[] paramDescriptors = CodeFlow.toDescriptors(parameterTypes); String[] parameterDescriptors = CodeFlow.toDescriptors(parameterTypes);
int parameterCount = parameterTypes.length;
if (executable.isVarArgs()) { if (executable.isVarArgs()) {
// The final parameter may or may not need packaging into an array, or nothing may // 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. // have been passed to satisfy the varargs which means something needs to be built.
int p = 0; // Current supplied argument being processed
int childCount = arguments.length; int varargsIndex = parameterCount - 1;
int argumentCount = arguments.length;
int p = 0; // Current supplied argument being processed
// Fulfill all the parameter requirements except the last one // Fulfill all the parameter requirements except the last one (the varargs array).
for (p = 0; p < paramDescriptors.length - 1; p++) { for (p = 0; p < varargsIndex; p++) {
cf.generateCodeForArgument(mv, arguments[p], paramDescriptors[p]); cf.generateCodeForArgument(mv, arguments[p], parameterDescriptors[p]);
} }
SpelNodeImpl lastChild = (childCount == 0 ? null : arguments[childCount - 1]); SpelNodeImpl lastArgument = (argumentCount != 0 ? arguments[argumentCount - 1] : null);
ClassLoader classLoader = executable.getDeclaringClass().getClassLoader(); ClassLoader classLoader = executable.getDeclaringClass().getClassLoader();
Class<?> lastChildType = (lastChild != null ? Class<?> lastArgumentType = (lastArgument != null ?
loadClassForExitDescriptor(lastChild.getExitDescriptor(), classLoader) : null); loadClassForExitDescriptor(lastArgument.getExitDescriptor(), classLoader) : null);
Class<?> lastParameterType = parameterTypes[parameterTypes.length - 1]; Class<?> lastParameterType = parameterTypes[varargsIndex];
// Determine if the final passed argument is already suitably packaged in array // Determine if the final passed argument is already suitably packaged in array
// form to be passed to the method // form to be passed to the method.
if (lastChild != null && lastChildType != null && lastParameterType.isAssignableFrom(lastChildType)) { if (lastArgument != null && lastArgumentType != null && lastParameterType.isAssignableFrom(lastArgumentType)) {
cf.generateCodeForArgument(mv, lastChild, paramDescriptors[p]); cf.generateCodeForArgument(mv, lastArgument, parameterDescriptors[p]);
} }
else { else {
String arrayType = paramDescriptors[paramDescriptors.length - 1]; String arrayComponentType = parameterDescriptors[varargsIndex];
arrayType = arrayType.substring(1); // trim the leading '[', may leave other '[' // Trim the leading '[', potentially leaving other '[' characters.
// build array big enough to hold remaining arguments arrayComponentType = arrayComponentType.substring(1);
CodeFlow.insertNewArrayCode(mv, childCount - p, arrayType); // Build array big enough to hold remaining arguments.
// Package up the remaining arguments into the array CodeFlow.insertNewArrayCode(mv, argumentCount - p, arrayComponentType);
int arrayindex = 0; // Package up the remaining arguments into the array.
while (p < childCount) { int arrayIndex = 0;
SpelNodeImpl child = arguments[p]; while (p < argumentCount) {
mv.visitInsn(DUP); mv.visitInsn(DUP);
CodeFlow.insertOptimalLoad(mv, arrayindex++); CodeFlow.insertOptimalLoad(mv, arrayIndex++);
cf.generateCodeForArgument(mv, child, arrayType); cf.generateCodeForArgument(mv, arguments[p++], arrayComponentType);
CodeFlow.insertArrayStore(mv, arrayType); CodeFlow.insertArrayStore(mv, arrayComponentType);
p++;
} }
} }
} }
else { else {
for (int i = 0; i < paramDescriptors.length;i++) { for (int i = 0; i < parameterCount; i++) {
cf.generateCodeForArgument(mv, arguments[i], paramDescriptors[i]); cf.generateCodeForArgument(mv, arguments[i], parameterDescriptors[i]);
} }
} }
} }
@ -308,8 +316,10 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
} }
/** /**
* Ask an argument to generate its bytecode and then follow it up * Generate bytecode that loads the supplied argument onto the stack.
* with any boxing/unboxing/checkcasting to ensure it matches the expected parameter descriptor. * <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 paramDesc}.
* @deprecated As of Spring Framework 6.2, in favor of * @deprecated As of Spring Framework 6.2, in favor of
* {@link CodeFlow#generateCodeForArgument(MethodVisitor, SpelNode, String)} * {@link CodeFlow#generateCodeForArgument(MethodVisitor, SpelNode, String)}
*/ */

70
spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java

@ -2642,14 +2642,12 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(20); assertThat(expression.getValue(context)).isEqualTo(20);
expression = parser.parseExpression("#appendChar('abc'.charAt(0),'abc'.charAt(1))"); expression = parser.parseExpression("#appendChar('abc'.charAt(0),'abc'.charAt(1))");
assertThat(expression.getValue(context)).isEqualTo("ab"); assertThat(expression.getValue(context)).isEqualTo("ab");
assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo("ab"); assertThat(expression.getValue(context)).isEqualTo("ab");
expression = parser.parseExpression("#append4('a','b','c')"); expression = parser.parseExpression("#append4('a','b','c')");
assertThat(expression.getValue(context).toString()).isEqualTo("a::bc"); assertThat(expression.getValue(context).toString()).isEqualTo("a::bc");
assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
@ -3259,7 +3257,6 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertThat(b).isTrue(); assertThat(b).isTrue();
assertThat(aa.gotComparedTo).isEqualTo(bb); assertThat(aa.gotComparedTo).isEqualTo(bb);
List<String> ls = new ArrayList<>(); List<String> ls = new ArrayList<>();
ls.add("foo"); ls.add("foo");
StandardEvaluationContext context = new StandardEvaluationContext(ls); StandardEvaluationContext context = new StandardEvaluationContext(ls);
@ -4682,7 +4679,6 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertThat(expression.getValue(Boolean.class)).isNull(); assertThat(expression.getValue(Boolean.class)).isNull();
} }
/** /**
* Test variants of using T(...) and static/non-static method/property/field references. * Test variants of using T(...) and static/non-static method/property/field references.
*/ */
@ -4775,7 +4771,6 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertThat(expression.getValue(StaticsHelper.sh)).isEqualTo("mb"); assertThat(expression.getValue(StaticsHelper.sh)).isEqualTo("mb");
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(StaticsHelper.sh)).isEqualTo("mb"); assertThat(expression.getValue(StaticsHelper.sh)).isEqualTo("mb");
} }
@Test @Test
@ -4863,86 +4858,89 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
context = new StandardEvaluationContext(new Object[] {person.getAge()}); context = new StandardEvaluationContext(new Object[] {person.getAge()});
context.setVariable("it", person); context.setVariable("it", person);
assertThat(ex.getValue(context, Boolean.class)).isTrue(); assertThat(ex.getValue(context, Boolean.class)).isTrue();
assertThat(ex.getValue(context, Boolean.class)).isTrue();
PersonInOtherPackage person2 = new PersonInOtherPackage(1); PersonInOtherPackage person2 = new PersonInOtherPackage(1);
ex = parser.parseRaw("#it?.age.equals([0])"); ex = parser.parseRaw("#it?.age.equals([0])");
context = new StandardEvaluationContext(new Object[] {person2.getAge()}); context = new StandardEvaluationContext(new Object[] {person2.getAge()});
context.setVariable("it", person2); context.setVariable("it", person2);
assertThat(ex.getValue(context, Boolean.class)).isTrue(); assertThat(ex.getValue(context, Boolean.class)).isTrue();
assertThat(ex.getValue(context, Boolean.class)).isTrue();
ex = parser.parseRaw("#it?.age.equals([0])"); ex = parser.parseRaw("#it?.age.equals([0])");
context = new StandardEvaluationContext(new Object[] {person2.getAge()}); context = new StandardEvaluationContext(new Object[] {person2.getAge()});
context.setVariable("it", person2); context.setVariable("it", person2);
assertThat((Boolean) ex.getValue(context)).isTrue(); assertThat((Boolean) ex.getValue(context)).isTrue();
assertThat((Boolean) ex.getValue(context)).isTrue();
} }
@Test @Test
void constructorReference() { void constructorReference() {
// simple ctor // simple constructor
expression = parser.parseExpression("new String('123')"); expression = parser.parseExpression("new String('123')");
assertThat(expression.getValue()).isEqualTo("123"); assertThat(expression.getValue()).isEqualTo("123");
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue()).isEqualTo("123"); assertThat(expression.getValue()).isEqualTo("123");
String testclass8 = "org.springframework.expression.spel.SpelCompilationCoverageTests$TestClass8"; String testclass8 = TestClass8.class.getName();
// multi arg ctor that includes primitives Object result;
// multi arg constructor that includes primitives
expression = parser.parseExpression("new " + testclass8 + "(42,'123',4.0d,true)"); expression = parser.parseExpression("new " + testclass8 + "(42,'123',4.0d,true)");
assertThat(expression.getValue().getClass().getName()).isEqualTo(testclass8); result = expression.getValue();
assertThat(result).isExactlyInstanceOf(TestClass8.class);
assertCanCompile(expression); assertCanCompile(expression);
Object o = expression.getValue(); result = expression.getValue();
assertThat(o.getClass().getName()).isEqualTo(testclass8); assertThat(result).isExactlyInstanceOf(TestClass8.class);
TestClass8 tc8 = (TestClass8) o; TestClass8 tc8 = (TestClass8) result;
assertThat(tc8.i).isEqualTo(42); assertThat(tc8.i).isEqualTo(42);
assertThat(tc8.s).isEqualTo("123"); assertThat(tc8.s).isEqualTo("123");
assertThat(tc8.d).isCloseTo(4.0d, within(0.5d)); assertThat(tc8.d).isCloseTo(4.0d, within(0.5d));
assertThat(tc8.z).isTrue(); assertThat(tc8.z).isTrue();
// no-arg ctor // no-arg constructor
expression = parser.parseExpression("new " + testclass8 + "()"); expression = parser.parseExpression("new " + testclass8 + "()");
assertThat(expression.getValue().getClass().getName()).isEqualTo(testclass8); result = expression.getValue();
assertThat(result).isExactlyInstanceOf(TestClass8.class);
assertCanCompile(expression); assertCanCompile(expression);
o = expression.getValue(); result = expression.getValue();
assertThat(o.getClass().getName()).isEqualTo(testclass8); assertThat(result).isExactlyInstanceOf(TestClass8.class);
// pass primitive to reference type ctor // pass primitive to reference type constructor
expression = parser.parseExpression("new " + testclass8 + "(42)"); expression = parser.parseExpression("new " + testclass8 + "(42)");
assertThat(expression.getValue().getClass().getName()).isEqualTo(testclass8); result = expression.getValue();
assertThat(result).isExactlyInstanceOf(TestClass8.class);
assertCanCompile(expression); assertCanCompile(expression);
o = expression.getValue(); result = expression.getValue();
assertThat(o.getClass().getName()).isEqualTo(testclass8); assertThat(result).isExactlyInstanceOf(TestClass8.class);
tc8 = (TestClass8) o; tc8 = (TestClass8) result;
assertThat(tc8.i).isEqualTo(42); assertThat(tc8.i).isEqualTo(42);
// varargs // varargs
expression = parser.parseExpression("new " + testclass8 + "(#root)"); expression = parser.parseExpression("new " + testclass8 + "(#root)");
Object[] objectArray = { "a", "b", "c" }; Object[] objectArray = { "a", "b", "c" };
o = expression.getValue(objectArray); result = expression.getValue(objectArray);
assertThat(o).isExactlyInstanceOf(TestClass8.class); assertThat(result).isExactlyInstanceOf(TestClass8.class);
assertCanCompile(expression); assertCanCompile(expression);
o = expression.getValue(objectArray); result = expression.getValue(objectArray);
assertThat(o).isExactlyInstanceOf(TestClass8.class); assertThat(result).isExactlyInstanceOf(TestClass8.class);
tc8 = (TestClass8) o; tc8 = (TestClass8) result;
assertThat(tc8.args).containsExactly("a", "b", "c"); assertThat(tc8.args).containsExactly("a", "b", "c");
// varargs with argument component type that is a subtype of the varargs component type. // varargs with argument component type that is a subtype of the varargs component type.
expression = parser.parseExpression("new " + testclass8 + "(#root)"); expression = parser.parseExpression("new " + testclass8 + "(#root)");
String[] stringArray = { "a", "b", "c" }; String[] stringArray = { "a", "b", "c" };
o = expression.getValue(stringArray); result = expression.getValue(stringArray);
assertThat(o).isExactlyInstanceOf(TestClass8.class); assertThat(result).isExactlyInstanceOf(TestClass8.class);
assertCanCompile(expression); assertCanCompile(expression);
o = expression.getValue(stringArray); result = expression.getValue(stringArray);
assertThat(o).isExactlyInstanceOf(TestClass8.class); assertThat(result).isExactlyInstanceOf(TestClass8.class);
tc8 = (TestClass8) o; tc8 = (TestClass8) result;
assertThat(tc8.args).containsExactly("a", "b", "c"); assertThat(tc8.args).containsExactly("a", "b", "c");
// private class, can't compile it // private class, can't compile it
String testclass9 = "org.springframework.expression.spel.SpelCompilationCoverageTests$TestClass9"; String testclass9 = TestClass9.class.getName();
expression = parser.parseExpression("new " + testclass9 + "(42)"); expression = parser.parseExpression("new " + testclass9 + "(42)");
assertThat(expression.getValue().getClass().getName()).isEqualTo(testclass9); result = expression.getValue();
assertThat(result).isExactlyInstanceOf(TestClass9.class);
assertCannotCompile(expression); assertCannotCompile(expression);
} }

Loading…
Cancel
Save