Simplify compilation of array indexing in SpEL's Indexer

This commit is contained in:
Sam Brannen 2024-03-23 12:35:25 +01:00
parent 7edd4e8e22
commit 2a1abb5553
2 changed files with 40 additions and 44 deletions

View File

@ -71,6 +71,9 @@ public class Indexer extends SpelNodeImpl {
@Nullable @Nullable
private IndexedType indexedType; private IndexedType indexedType;
@Nullable
private volatile String arrayTypeDescriptor;
// These fields are used when the indexer is being used as a property read accessor. // 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 // 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 // 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 @Override
public boolean isCompilable() { public boolean isCompilable() {
if (this.indexedType == IndexedType.ARRAY) { if (this.indexedType == IndexedType.ARRAY) {
return (this.exitTypeDescriptor != null); return (this.exitTypeDescriptor != null && this.arrayTypeDescriptor != null);
} }
SpelNodeImpl index = this.children[0]; SpelNodeImpl index = this.children[0];
if (this.indexedType == IndexedType.LIST) { if (this.indexedType == IndexedType.LIST) {
@ -233,6 +236,7 @@ public class Indexer extends SpelNodeImpl {
@Override @Override
public void generateCode(MethodVisitor mv, CodeFlow cf) { public void generateCode(MethodVisitor mv, CodeFlow cf) {
String exitTypeDescriptor = this.exitTypeDescriptor;
String descriptor = cf.lastDescriptor(); String descriptor = cf.lastDescriptor();
if (descriptor == null) { if (descriptor == null) {
// Stack is empty, should use context object // Stack is empty, should use context object
@ -242,48 +246,19 @@ public class Indexer extends SpelNodeImpl {
SpelNodeImpl index = this.children[0]; SpelNodeImpl index = this.children[0];
if (this.indexedType == IndexedType.ARRAY) { if (this.indexedType == IndexedType.ARRAY) {
String exitTypeDescriptor = this.exitTypeDescriptor; String arrayTypeDescriptor = this.arrayTypeDescriptor;
Assert.state(exitTypeDescriptor != null, "Array not compilable without descriptor"); Assert.state(exitTypeDescriptor != null && arrayTypeDescriptor != null,
int insn = switch (exitTypeDescriptor) { "Array not compilable without descriptors");
case "D" -> { CodeFlow.insertCheckCast(mv, arrayTypeDescriptor);
mv.visitTypeInsn(CHECKCAST, "[D"); int insn = switch (arrayTypeDescriptor) {
yield DALOAD; case "[D" -> DALOAD;
} case "[F" -> FALOAD;
case "F" -> { case "[J" -> LALOAD;
mv.visitTypeInsn(CHECKCAST, "[F"); case "[I" -> IALOAD;
yield FALOAD; case "[S" -> SALOAD;
} case "[B", "[Z" -> BALOAD; // byte[] & boolean[] are both loaded via BALOAD
case "J" -> { case "[C" -> CALOAD;
mv.visitTypeInsn(CHECKCAST, "[J"); default -> AALOAD;
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;
}
}; };
cf.enterCompilationScope(); cf.enterCompilationScope();
@ -329,7 +304,7 @@ public class Indexer extends SpelNodeImpl {
compilablePropertyAccessor.generateCode(propertyName, mv, cf); compilablePropertyAccessor.generateCode(propertyName, mv, cf);
} }
cf.pushDescriptor(this.exitTypeDescriptor); cf.pushDescriptor(exitTypeDescriptor);
} }
@Override @Override
@ -394,48 +369,56 @@ public class Indexer extends SpelNodeImpl {
boolean[] array = (boolean[]) ctx; boolean[] array = (boolean[]) ctx;
checkAccess(array.length, idx); checkAccess(array.length, idx);
this.exitTypeDescriptor = "Z"; this.exitTypeDescriptor = "Z";
this.arrayTypeDescriptor = "[Z";
return array[idx]; return array[idx];
} }
else if (arrayComponentType == byte.class) { else if (arrayComponentType == byte.class) {
byte[] array = (byte[]) ctx; byte[] array = (byte[]) ctx;
checkAccess(array.length, idx); checkAccess(array.length, idx);
this.exitTypeDescriptor = "B"; this.exitTypeDescriptor = "B";
this.arrayTypeDescriptor = "[B";
return array[idx]; return array[idx];
} }
else if (arrayComponentType == char.class) { else if (arrayComponentType == char.class) {
char[] array = (char[]) ctx; char[] array = (char[]) ctx;
checkAccess(array.length, idx); checkAccess(array.length, idx);
this.exitTypeDescriptor = "C"; this.exitTypeDescriptor = "C";
this.arrayTypeDescriptor = "[C";
return array[idx]; return array[idx];
} }
else if (arrayComponentType == double.class) { else if (arrayComponentType == double.class) {
double[] array = (double[]) ctx; double[] array = (double[]) ctx;
checkAccess(array.length, idx); checkAccess(array.length, idx);
this.exitTypeDescriptor = "D"; this.exitTypeDescriptor = "D";
this.arrayTypeDescriptor = "[D";
return array[idx]; return array[idx];
} }
else if (arrayComponentType == float.class) { else if (arrayComponentType == float.class) {
float[] array = (float[]) ctx; float[] array = (float[]) ctx;
checkAccess(array.length, idx); checkAccess(array.length, idx);
this.exitTypeDescriptor = "F"; this.exitTypeDescriptor = "F";
this.arrayTypeDescriptor = "[F";
return array[idx]; return array[idx];
} }
else if (arrayComponentType == int.class) { else if (arrayComponentType == int.class) {
int[] array = (int[]) ctx; int[] array = (int[]) ctx;
checkAccess(array.length, idx); checkAccess(array.length, idx);
this.exitTypeDescriptor = "I"; this.exitTypeDescriptor = "I";
this.arrayTypeDescriptor = "[I";
return array[idx]; return array[idx];
} }
else if (arrayComponentType == long.class) { else if (arrayComponentType == long.class) {
long[] array = (long[]) ctx; long[] array = (long[]) ctx;
checkAccess(array.length, idx); checkAccess(array.length, idx);
this.exitTypeDescriptor = "J"; this.exitTypeDescriptor = "J";
this.arrayTypeDescriptor = "[J";
return array[idx]; return array[idx];
} }
else if (arrayComponentType == short.class) { else if (arrayComponentType == short.class) {
short[] array = (short[]) ctx; short[] array = (short[]) ctx;
checkAccess(array.length, idx); checkAccess(array.length, idx);
this.exitTypeDescriptor = "S"; this.exitTypeDescriptor = "S";
this.arrayTypeDescriptor = "[S";
return array[idx]; return array[idx];
} }
else { else {
@ -443,6 +426,7 @@ public class Indexer extends SpelNodeImpl {
checkAccess(array.length, idx); checkAccess(array.length, idx);
Object retValue = array[idx]; Object retValue = array[idx];
this.exitTypeDescriptor = CodeFlow.toDescriptor(arrayComponentType); this.exitTypeDescriptor = CodeFlow.toDescriptor(arrayComponentType);
this.arrayTypeDescriptor = CodeFlow.toDescriptor(array.getClass());
return retValue; return retValue;
} }
} }

View File

@ -652,6 +652,18 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertThat(getAst().getExitDescriptor()).isEqualTo("I"); 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 @Test
void indexIntoStringCannotBeCompiled() { void indexIntoStringCannotBeCompiled() {
String text = "enigma"; String text = "enigma";