Simplify compilation of array indexing in SpEL's Indexer
This commit is contained in:
parent
7edd4e8e22
commit
2a1abb5553
|
@ -71,6 +71,9 @@ public class Indexer extends SpelNodeImpl {
|
|||
@Nullable
|
||||
private IndexedType indexedType;
|
||||
|
||||
@Nullable
|
||||
private volatile String arrayTypeDescriptor;
|
||||
|
||||
// These fields are used when the indexer is being used as a property read accessor.
|
||||
// If the name and target type match these cached values then the cachedReadAccessor
|
||||
// is used to read the property. If they do not match, the correct accessor is
|
||||
|
@ -212,7 +215,7 @@ public class Indexer extends SpelNodeImpl {
|
|||
@Override
|
||||
public boolean isCompilable() {
|
||||
if (this.indexedType == IndexedType.ARRAY) {
|
||||
return (this.exitTypeDescriptor != null);
|
||||
return (this.exitTypeDescriptor != null && this.arrayTypeDescriptor != null);
|
||||
}
|
||||
SpelNodeImpl index = this.children[0];
|
||||
if (this.indexedType == IndexedType.LIST) {
|
||||
|
@ -233,6 +236,7 @@ public class Indexer extends SpelNodeImpl {
|
|||
|
||||
@Override
|
||||
public void generateCode(MethodVisitor mv, CodeFlow cf) {
|
||||
String exitTypeDescriptor = this.exitTypeDescriptor;
|
||||
String descriptor = cf.lastDescriptor();
|
||||
if (descriptor == null) {
|
||||
// Stack is empty, should use context object
|
||||
|
@ -242,48 +246,19 @@ public class Indexer extends SpelNodeImpl {
|
|||
SpelNodeImpl index = this.children[0];
|
||||
|
||||
if (this.indexedType == IndexedType.ARRAY) {
|
||||
String exitTypeDescriptor = this.exitTypeDescriptor;
|
||||
Assert.state(exitTypeDescriptor != null, "Array not compilable without descriptor");
|
||||
int insn = switch (exitTypeDescriptor) {
|
||||
case "D" -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "[D");
|
||||
yield DALOAD;
|
||||
}
|
||||
case "F" -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "[F");
|
||||
yield FALOAD;
|
||||
}
|
||||
case "J" -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "[J");
|
||||
yield LALOAD;
|
||||
}
|
||||
case "I" -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "[I");
|
||||
yield IALOAD;
|
||||
}
|
||||
case "S" -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "[S");
|
||||
yield SALOAD;
|
||||
}
|
||||
case "B" -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "[B");
|
||||
// byte and boolean arrays are both loaded via BALOAD
|
||||
yield BALOAD;
|
||||
}
|
||||
case "Z" -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "[Z");
|
||||
// byte and boolean arrays are both loaded via BALOAD
|
||||
yield BALOAD;
|
||||
}
|
||||
case "C" -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "[C");
|
||||
yield CALOAD;
|
||||
}
|
||||
default -> {
|
||||
mv.visitTypeInsn(CHECKCAST, "["+ exitTypeDescriptor +
|
||||
(CodeFlow.isPrimitiveArray(exitTypeDescriptor) ? "" : ";"));
|
||||
yield AALOAD;
|
||||
}
|
||||
String arrayTypeDescriptor = this.arrayTypeDescriptor;
|
||||
Assert.state(exitTypeDescriptor != null && arrayTypeDescriptor != null,
|
||||
"Array not compilable without descriptors");
|
||||
CodeFlow.insertCheckCast(mv, arrayTypeDescriptor);
|
||||
int insn = switch (arrayTypeDescriptor) {
|
||||
case "[D" -> DALOAD;
|
||||
case "[F" -> FALOAD;
|
||||
case "[J" -> LALOAD;
|
||||
case "[I" -> IALOAD;
|
||||
case "[S" -> SALOAD;
|
||||
case "[B", "[Z" -> BALOAD; // byte[] & boolean[] are both loaded via BALOAD
|
||||
case "[C" -> CALOAD;
|
||||
default -> AALOAD;
|
||||
};
|
||||
|
||||
cf.enterCompilationScope();
|
||||
|
@ -329,7 +304,7 @@ public class Indexer extends SpelNodeImpl {
|
|||
compilablePropertyAccessor.generateCode(propertyName, mv, cf);
|
||||
}
|
||||
|
||||
cf.pushDescriptor(this.exitTypeDescriptor);
|
||||
cf.pushDescriptor(exitTypeDescriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -394,48 +369,56 @@ public class Indexer extends SpelNodeImpl {
|
|||
boolean[] array = (boolean[]) ctx;
|
||||
checkAccess(array.length, idx);
|
||||
this.exitTypeDescriptor = "Z";
|
||||
this.arrayTypeDescriptor = "[Z";
|
||||
return array[idx];
|
||||
}
|
||||
else if (arrayComponentType == byte.class) {
|
||||
byte[] array = (byte[]) ctx;
|
||||
checkAccess(array.length, idx);
|
||||
this.exitTypeDescriptor = "B";
|
||||
this.arrayTypeDescriptor = "[B";
|
||||
return array[idx];
|
||||
}
|
||||
else if (arrayComponentType == char.class) {
|
||||
char[] array = (char[]) ctx;
|
||||
checkAccess(array.length, idx);
|
||||
this.exitTypeDescriptor = "C";
|
||||
this.arrayTypeDescriptor = "[C";
|
||||
return array[idx];
|
||||
}
|
||||
else if (arrayComponentType == double.class) {
|
||||
double[] array = (double[]) ctx;
|
||||
checkAccess(array.length, idx);
|
||||
this.exitTypeDescriptor = "D";
|
||||
this.arrayTypeDescriptor = "[D";
|
||||
return array[idx];
|
||||
}
|
||||
else if (arrayComponentType == float.class) {
|
||||
float[] array = (float[]) ctx;
|
||||
checkAccess(array.length, idx);
|
||||
this.exitTypeDescriptor = "F";
|
||||
this.arrayTypeDescriptor = "[F";
|
||||
return array[idx];
|
||||
}
|
||||
else if (arrayComponentType == int.class) {
|
||||
int[] array = (int[]) ctx;
|
||||
checkAccess(array.length, idx);
|
||||
this.exitTypeDescriptor = "I";
|
||||
this.arrayTypeDescriptor = "[I";
|
||||
return array[idx];
|
||||
}
|
||||
else if (arrayComponentType == long.class) {
|
||||
long[] array = (long[]) ctx;
|
||||
checkAccess(array.length, idx);
|
||||
this.exitTypeDescriptor = "J";
|
||||
this.arrayTypeDescriptor = "[J";
|
||||
return array[idx];
|
||||
}
|
||||
else if (arrayComponentType == short.class) {
|
||||
short[] array = (short[]) ctx;
|
||||
checkAccess(array.length, idx);
|
||||
this.exitTypeDescriptor = "S";
|
||||
this.arrayTypeDescriptor = "[S";
|
||||
return array[idx];
|
||||
}
|
||||
else {
|
||||
|
@ -443,6 +426,7 @@ public class Indexer extends SpelNodeImpl {
|
|||
checkAccess(array.length, idx);
|
||||
Object retValue = array[idx];
|
||||
this.exitTypeDescriptor = CodeFlow.toDescriptor(arrayComponentType);
|
||||
this.arrayTypeDescriptor = CodeFlow.toDescriptor(array.getClass());
|
||||
return retValue;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -652,6 +652,18 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
|
|||
assertThat(getAst().getExitDescriptor()).isEqualTo("I");
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexIntoSetCannotBeCompiled() {
|
||||
Set<Integer> set = Set.of(42);
|
||||
|
||||
expression = parser.parseExpression("[0]");
|
||||
|
||||
assertThat(expression.getValue(set)).isEqualTo(42);
|
||||
assertCannotCompile(expression);
|
||||
assertThat(expression.getValue(set)).isEqualTo(42);
|
||||
assertThat(getAst().getExitDescriptor()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexIntoStringCannotBeCompiled() {
|
||||
String text = "enigma";
|
||||
|
|
Loading…
Reference in New Issue