Browse Source

Use switch expressions in SpEL's CodeFlow

Closes gh-29020
pull/29703/head
Roman Ivanov 3 years ago committed by Sam Brannen
parent
commit
dcda127b60
  1. 399
      spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -42,7 +42,7 @@ import org.springframework.util.CollectionUtils;
public class CodeFlow implements Opcodes { public class CodeFlow implements Opcodes {
/** /**
* Name of the class being generated. Typically used when generating code * Name of the class being generated. Typically, used when generating code
* that accesses freshly generated fields on the generated type. * that accesses freshly generated fields on the generated type.
*/ */
private final String className; private final String className;
@ -242,56 +242,56 @@ public class CodeFlow implements Opcodes {
return; return;
} }
switch (ch) { switch (ch) {
case 'Z': case 'Z' -> {
if (!stackDescriptor.equals("Ljava/lang/Boolean")) { if (!stackDescriptor.equals("Ljava/lang/Boolean")) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean"); mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
} }
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
break; }
case 'B': case 'B' -> {
if (!stackDescriptor.equals("Ljava/lang/Byte")) { if (!stackDescriptor.equals("Ljava/lang/Byte")) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Byte"); mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
} }
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
break; }
case 'C': case 'C' -> {
if (!stackDescriptor.equals("Ljava/lang/Character")) { if (!stackDescriptor.equals("Ljava/lang/Character")) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Character"); mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
} }
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
break; }
case 'D': case 'D' -> {
if (!stackDescriptor.equals("Ljava/lang/Double")) { if (!stackDescriptor.equals("Ljava/lang/Double")) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Double"); mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
} }
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
break; }
case 'F': case 'F' -> {
if (!stackDescriptor.equals("Ljava/lang/Float")) { if (!stackDescriptor.equals("Ljava/lang/Float")) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Float"); mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
} }
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
break; }
case 'I': case 'I' -> {
if (!stackDescriptor.equals("Ljava/lang/Integer")) { if (!stackDescriptor.equals("Ljava/lang/Integer")) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Integer"); mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
} }
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
break; }
case 'J': case 'J' -> {
if (!stackDescriptor.equals("Ljava/lang/Long")) { if (!stackDescriptor.equals("Ljava/lang/Long")) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Long"); mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
} }
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
break; }
case 'S': case 'S' -> {
if (!stackDescriptor.equals("Ljava/lang/Short")) { if (!stackDescriptor.equals("Ljava/lang/Short")) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Short"); mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
} }
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
break; }
default: default ->
throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'"); throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'");
} }
} }
@ -303,39 +303,37 @@ public class CodeFlow implements Opcodes {
*/ */
public static void insertUnboxNumberInsns( public static void insertUnboxNumberInsns(
MethodVisitor mv, char targetDescriptor, @Nullable String stackDescriptor) { MethodVisitor mv, char targetDescriptor, @Nullable String stackDescriptor) {
if (stackDescriptor == null) { if (stackDescriptor == null) {
return; return;
} }
switch (targetDescriptor) { switch (targetDescriptor) {
case 'D': case 'D' -> {
if (stackDescriptor.equals("Ljava/lang/Object")) { if (stackDescriptor.equals("Ljava/lang/Object")) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); mv.visitTypeInsn(CHECKCAST, "java/lang/Number");
} }
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "doubleValue", "()D", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "doubleValue", "()D", false);
break; }
case 'F': case 'F' -> {
if (stackDescriptor.equals("Ljava/lang/Object")) { if (stackDescriptor.equals("Ljava/lang/Object")) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); mv.visitTypeInsn(CHECKCAST, "java/lang/Number");
} }
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "floatValue", "()F", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "floatValue", "()F", false);
break; }
case 'J': case 'J' -> {
if (stackDescriptor.equals("Ljava/lang/Object")) { if (stackDescriptor.equals("Ljava/lang/Object")) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); mv.visitTypeInsn(CHECKCAST, "java/lang/Number");
} }
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "longValue", "()J", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "longValue", "()J", false);
break; }
case 'I': case 'I' -> {
if (stackDescriptor.equals("Ljava/lang/Object")) { if (stackDescriptor.equals("Ljava/lang/Object")) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); mv.visitTypeInsn(CHECKCAST, "java/lang/Number");
} }
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "intValue", "()I", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "intValue", "()I", false);
break; }
// does not handle Z, B, C, S // does not handle Z, B, C, S
default: default ->
throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + targetDescriptor + "'"); throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + targetDescriptor + "'");
} }
} }
@ -346,74 +344,51 @@ public class CodeFlow implements Opcodes {
* @param stackDescriptor the descriptor of the operand on top of the stack * @param stackDescriptor the descriptor of the operand on top of the stack
*/ */
public static void insertAnyNecessaryTypeConversionBytecodes(MethodVisitor mv, char targetDescriptor, String stackDescriptor) { public static void insertAnyNecessaryTypeConversionBytecodes(MethodVisitor mv, char targetDescriptor, String stackDescriptor) {
if (CodeFlow.isPrimitive(stackDescriptor)) { if (!CodeFlow.isPrimitive(stackDescriptor)) return;
char stackTop = stackDescriptor.charAt(0); char stackTop = stackDescriptor.charAt(0);
if (stackTop == 'I' || stackTop == 'B' || stackTop == 'S' || stackTop == 'C') { switch (stackTop){
if (targetDescriptor == 'D') { case 'I', 'B', 'S', 'C' -> {
mv.visitInsn(I2D); switch (targetDescriptor) {
} case 'D' -> mv.visitInsn(I2D);
else if (targetDescriptor == 'F') { case 'F' -> mv.visitInsn(I2F);
mv.visitInsn(I2F); case 'J' -> mv.visitInsn(I2L);
} case 'I' -> { //nop
else if (targetDescriptor == 'J') { }
mv.visitInsn(I2L); default ->
} throw new IllegalStateException("Cannot get from " + stackTop + " to " + targetDescriptor);
else if (targetDescriptor == 'I') {
// nop
}
else {
throw new IllegalStateException("Cannot get from " + stackTop + " to " + targetDescriptor);
} }
} }
else if (stackTop == 'J') { case 'J' -> {
if (targetDescriptor == 'D') { switch (targetDescriptor) {
mv.visitInsn(L2D); case 'D' -> mv.visitInsn(L2D);
} case 'F' -> mv.visitInsn(L2F);
else if (targetDescriptor == 'F') { case 'J' -> { //nop
mv.visitInsn(L2F); }
} case 'I' -> mv.visitInsn(L2I);
else if (targetDescriptor == 'J') { default -> throw new IllegalStateException("Cannot get from " + stackTop +
// nop " to " + targetDescriptor);
}
else if (targetDescriptor == 'I') {
mv.visitInsn(L2I);
}
else {
throw new IllegalStateException("Cannot get from " + stackTop + " to " + targetDescriptor);
} }
} }
else if (stackTop == 'F') { case 'F' -> {
if (targetDescriptor == 'D') { switch (targetDescriptor) {
mv.visitInsn(F2D); case 'D' -> mv.visitInsn(F2D);
} case 'F' -> { //nop
else if (targetDescriptor == 'F') { }
// nop case 'J' -> mv.visitInsn(F2L);
} case 'I' -> mv.visitInsn(F2I);
else if (targetDescriptor == 'J') { default -> throw new IllegalStateException("Cannot get from " + stackTop +
mv.visitInsn(F2L); " to " + targetDescriptor);
}
else if (targetDescriptor == 'I') {
mv.visitInsn(F2I);
}
else {
throw new IllegalStateException("Cannot get from " + stackTop + " to " + targetDescriptor);
} }
} }
else if (stackTop == 'D') { case 'D' -> {
if (targetDescriptor == 'D') { switch (targetDescriptor) {
// nop case 'D' -> { //nop
} }
else if (targetDescriptor == 'F') { case 'F' -> mv.visitInsn(D2F);
mv.visitInsn(D2F); case 'J' -> mv.visitInsn(D2L);
} case 'I' -> mv.visitInsn(D2I);
else if (targetDescriptor == 'J') { default -> throw new IllegalStateException("Cannot get from " + stackDescriptor +
mv.visitInsn(D2L); " to " + targetDescriptor);
}
else if (targetDescriptor == 'I') {
mv.visitInsn(D2I);
}
else {
throw new IllegalStateException("Cannot get from " + stackDescriptor + " to " + targetDescriptor);
} }
} }
} }
@ -578,42 +553,25 @@ public class CodeFlow implements Opcodes {
return true; return true;
} }
if (desc1.length() == 1) { if (desc1.length() == 1) {
if (desc1.equals("Z")) { return checkPairs(desc1, desc2);
return desc2.equals("Ljava/lang/Boolean");
}
else if (desc1.equals("D")) {
return desc2.equals("Ljava/lang/Double");
}
else if (desc1.equals("F")) {
return desc2.equals("Ljava/lang/Float");
}
else if (desc1.equals("I")) {
return desc2.equals("Ljava/lang/Integer");
}
else if (desc1.equals("J")) {
return desc2.equals("Ljava/lang/Long");
}
} }
else if (desc2.length() == 1) { else if (desc2.length() == 1) {
if (desc2.equals("Z")) { return checkPairs(desc2, desc1);
return desc1.equals("Ljava/lang/Boolean");
}
else if (desc2.equals("D")) {
return desc1.equals("Ljava/lang/Double");
}
else if (desc2.equals("F")) {
return desc1.equals("Ljava/lang/Float");
}
else if (desc2.equals("I")) {
return desc1.equals("Ljava/lang/Integer");
}
else if (desc2.equals("J")) {
return desc1.equals("Ljava/lang/Long");
}
} }
return false; return false;
} }
private static boolean checkPairs(String desc1, String desc2) {
return switch (desc1) {
case "Z" -> desc2.equals("Ljava/lang/Boolean");
case "D" -> desc2.equals("Ljava/lang/Double");
case "F" -> desc2.equals("Ljava/lang/Float");
case "I" -> desc2.equals("Ljava/lang/Integer");
case "J" -> desc2.equals("Ljava/lang/Long");
default -> false;
};
}
/** /**
* Determine if the supplied descriptor is for a supported number type or boolean. The * Determine if the supplied descriptor is for a supported number type or boolean. The
* compilation process only (currently) supports certain number types. These are * compilation process only (currently) supports certain number types. These are
@ -647,9 +605,7 @@ public class CodeFlow implements Opcodes {
} }
if (descriptor.startsWith("Ljava/lang/")) { if (descriptor.startsWith("Ljava/lang/")) {
String name = descriptor.substring("Ljava/lang/".length()); String name = descriptor.substring("Ljava/lang/".length());
if (name.equals("Double") || name.equals("Float") || name.equals("Integer") || name.equals("Long")) { return name.equals("Double") || name.equals("Float") || name.equals("Integer") || name.equals("Long");
return true;
}
} }
return false; return false;
} }
@ -673,33 +629,17 @@ public class CodeFlow implements Opcodes {
if (descriptor.length() == 1) { if (descriptor.length() == 1) {
return descriptor.charAt(0); return descriptor.charAt(0);
} }
else if (descriptor.equals("Ljava/lang/Boolean")) { return switch (descriptor) {
return 'Z'; case "Ljava/lang/Double" -> 'D';
} case "Ljava/lang/Float" -> 'F';
else if (descriptor.equals("Ljava/lang/Byte")) { case "Ljava/lang/Integer" -> 'I';
return 'B'; case "Ljava/lang/Long" -> 'J';
} case "Ljava/lang/Boolean" -> 'Z';
else if (descriptor.equals("Ljava/lang/Character")) { case "Ljava/lang/Character" -> 'C';
return 'C'; case "Ljava/lang/Byte" -> 'B';
} case "Ljava/lang/Short" -> 'S';
else if (descriptor.equals("Ljava/lang/Double")) { default -> throw new IllegalStateException("No primitive for '" + descriptor + "'");
return 'D'; };
}
else if (descriptor.equals("Ljava/lang/Float")) {
return 'F';
}
else if (descriptor.equals("Ljava/lang/Integer")) {
return 'I';
}
else if (descriptor.equals("Ljava/lang/Long")) {
return 'J';
}
else if (descriptor.equals("Ljava/lang/Short")) {
return 'S';
}
else {
throw new IllegalStateException("No primitive for '" + descriptor + "'");
}
} }
/** /**
@ -746,37 +686,26 @@ public class CodeFlow implements Opcodes {
*/ */
public static void insertBoxIfNecessary(MethodVisitor mv, char ch) { public static void insertBoxIfNecessary(MethodVisitor mv, char ch) {
switch (ch) { switch (ch) {
case 'Z': case 'Z' ->
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
break; case 'B' ->
case 'B': mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false); case 'C' ->
break; mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
case 'C': case 'D' ->
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
break; case 'F' ->
case 'D': mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false); case 'I' ->
break; mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
case 'F': case 'J' ->
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
break; case 'S' ->
case 'I': mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); case 'L', 'V', '[' -> {
break;
case 'J':
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
break;
case 'S':
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
break;
case 'L':
case 'V':
case '[':
// no box needed // no box needed
break; }
default: default -> throw new IllegalArgumentException("Boxing should not be attempted for descriptor '" + ch + "'");
throw new IllegalArgumentException("Boxing should not be attempted for descriptor '" + ch + "'");
} }
} }
@ -793,19 +722,13 @@ public class CodeFlow implements Opcodes {
case 3: case 3:
return "I"; return "I";
case 4: case 4:
if (name.equals("byte")) { return switch (name) {
return "B"; case "byte" -> "B";
} case "char" -> "C";
else if (name.equals("char")) { case "long" -> "J";
return "C"; case "void" -> "V";
} default -> throw new IllegalStateException("Unexpected value: " + name);
else if (name.equals("long")) { };
return "J";
}
else if (name.equals("void")) {
return "V";
}
break;
case 5: case 5:
if (name.equals("float")) { if (name.equals("float")) {
return "F"; return "F";
@ -824,6 +747,7 @@ public class CodeFlow implements Opcodes {
return "Z"; return "Z";
} }
break; break;
default: throw new IllegalArgumentException("Unknown primitive type: " + name);
} }
} }
else { else {
@ -906,33 +830,14 @@ public class CodeFlow implements Opcodes {
public static void insertArrayStore(MethodVisitor mv, String arrayElementType) { public static void insertArrayStore(MethodVisitor mv, String arrayElementType) {
if (arrayElementType.length()==1) { if (arrayElementType.length()==1) {
switch (arrayElementType.charAt(0)) { switch (arrayElementType.charAt(0)) {
case 'I': case 'B', 'Z' -> mv.visitInsn(BASTORE);
mv.visitInsn(IASTORE); case 'I' -> mv.visitInsn(IASTORE);
break; case 'J' -> mv.visitInsn(LASTORE);
case 'J': case 'F' -> mv.visitInsn(FASTORE);
mv.visitInsn(LASTORE); case 'D' -> mv.visitInsn(DASTORE);
break; case 'C' -> mv.visitInsn(CASTORE);
case 'F': case 'S' -> mv.visitInsn(SASTORE);
mv.visitInsn(FASTORE); default -> throw new IllegalArgumentException("Unexpected arraytype " + arrayElementType.charAt(0));
break;
case 'D':
mv.visitInsn(DASTORE);
break;
case 'B':
mv.visitInsn(BASTORE);
break;
case 'C':
mv.visitInsn(CASTORE);
break;
case 'S':
mv.visitInsn(SASTORE);
break;
case 'Z':
mv.visitInsn(BASTORE);
break;
default:
throw new IllegalArgumentException(
"Unexpected arraytype " + arrayElementType.charAt(0));
} }
} }
else { else {
@ -946,18 +851,17 @@ public class CodeFlow implements Opcodes {
* @return the T tag to use for NEWARRAY * @return the T tag to use for NEWARRAY
*/ */
public static int arrayCodeFor(String arraytype) { public static int arrayCodeFor(String arraytype) {
switch (arraytype.charAt(0)) { return switch (arraytype.charAt(0)) {
case 'I': return T_INT; case 'I' -> T_INT;
case 'J': return T_LONG; case 'J' -> T_LONG;
case 'F': return T_FLOAT; case 'F' -> T_FLOAT;
case 'D': return T_DOUBLE; case 'D' -> T_DOUBLE;
case 'B': return T_BYTE; case 'B' -> T_BYTE;
case 'C': return T_CHAR; case 'C' -> T_CHAR;
case 'S': return T_SHORT; case 'S' -> T_SHORT;
case 'Z': return T_BOOLEAN; case 'Z' -> T_BOOLEAN;
default: default -> throw new IllegalArgumentException("Unexpected arraytype " + arraytype.charAt(0));
throw new IllegalArgumentException("Unexpected arraytype " + arraytype.charAt(0)); };
}
} }
/** /**
@ -1026,18 +930,17 @@ public class CodeFlow implements Opcodes {
} }
public static String toBoxedDescriptor(String primitiveDescriptor) { public static String toBoxedDescriptor(String primitiveDescriptor) {
switch (primitiveDescriptor.charAt(0)) { return switch (primitiveDescriptor.charAt(0)) {
case 'I': return "Ljava/lang/Integer"; case 'I' -> "Ljava/lang/Integer";
case 'J': return "Ljava/lang/Long"; case 'J' -> "Ljava/lang/Long";
case 'F': return "Ljava/lang/Float"; case 'F' -> "Ljava/lang/Float";
case 'D': return "Ljava/lang/Double"; case 'D' -> "Ljava/lang/Double";
case 'B': return "Ljava/lang/Byte"; case 'B' -> "Ljava/lang/Byte";
case 'C': return "Ljava/lang/Character"; case 'C' -> "Ljava/lang/Character";
case 'S': return "Ljava/lang/Short"; case 'S' -> "Ljava/lang/Short";
case 'Z': return "Ljava/lang/Boolean"; case 'Z' -> "Ljava/lang/Boolean";
default: default -> throw new IllegalArgumentException("Unexpected non primitive descriptor " + primitiveDescriptor);
throw new IllegalArgumentException("Unexpected non primitive descriptor " + primitiveDescriptor); };
}
} }

Loading…
Cancel
Save