Skip to content

Commit 2a1abb5

Browse files
committed
Simplify compilation of array indexing in SpEL's Indexer
1 parent 7edd4e8 commit 2a1abb5

File tree

2 files changed

+40
-44
lines changed

2 files changed

+40
-44
lines changed

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

+28-44
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ private enum IndexedType {ARRAY, LIST, MAP, STRING, OBJECT}
7171
@Nullable
7272
private IndexedType indexedType;
7373

74+
@Nullable
75+
private volatile String arrayTypeDescriptor;
76+
7477
// These fields are used when the indexer is being used as a property read accessor.
7578
// If the name and target type match these cached values then the cachedReadAccessor
7679
// is used to read the property. If they do not match, the correct accessor is
@@ -212,7 +215,7 @@ else if (target instanceof Collection<?> collection) {
212215
@Override
213216
public boolean isCompilable() {
214217
if (this.indexedType == IndexedType.ARRAY) {
215-
return (this.exitTypeDescriptor != null);
218+
return (this.exitTypeDescriptor != null && this.arrayTypeDescriptor != null);
216219
}
217220
SpelNodeImpl index = this.children[0];
218221
if (this.indexedType == IndexedType.LIST) {
@@ -233,6 +236,7 @@ else if (this.indexedType == IndexedType.OBJECT) {
233236

234237
@Override
235238
public void generateCode(MethodVisitor mv, CodeFlow cf) {
239+
String exitTypeDescriptor = this.exitTypeDescriptor;
236240
String descriptor = cf.lastDescriptor();
237241
if (descriptor == null) {
238242
// Stack is empty, should use context object
@@ -242,48 +246,19 @@ public void generateCode(MethodVisitor mv, CodeFlow cf) {
242246
SpelNodeImpl index = this.children[0];
243247

244248
if (this.indexedType == IndexedType.ARRAY) {
245-
String exitTypeDescriptor = this.exitTypeDescriptor;
246-
Assert.state(exitTypeDescriptor != null, "Array not compilable without descriptor");
247-
int insn = switch (exitTypeDescriptor) {
248-
case "D" -> {
249-
mv.visitTypeInsn(CHECKCAST, "[D");
250-
yield DALOAD;
251-
}
252-
case "F" -> {
253-
mv.visitTypeInsn(CHECKCAST, "[F");
254-
yield FALOAD;
255-
}
256-
case "J" -> {
257-
mv.visitTypeInsn(CHECKCAST, "[J");
258-
yield LALOAD;
259-
}
260-
case "I" -> {
261-
mv.visitTypeInsn(CHECKCAST, "[I");
262-
yield IALOAD;
263-
}
264-
case "S" -> {
265-
mv.visitTypeInsn(CHECKCAST, "[S");
266-
yield SALOAD;
267-
}
268-
case "B" -> {
269-
mv.visitTypeInsn(CHECKCAST, "[B");
270-
// byte and boolean arrays are both loaded via BALOAD
271-
yield BALOAD;
272-
}
273-
case "Z" -> {
274-
mv.visitTypeInsn(CHECKCAST, "[Z");
275-
// byte and boolean arrays are both loaded via BALOAD
276-
yield BALOAD;
277-
}
278-
case "C" -> {
279-
mv.visitTypeInsn(CHECKCAST, "[C");
280-
yield CALOAD;
281-
}
282-
default -> {
283-
mv.visitTypeInsn(CHECKCAST, "["+ exitTypeDescriptor +
284-
(CodeFlow.isPrimitiveArray(exitTypeDescriptor) ? "" : ";"));
285-
yield AALOAD;
286-
}
249+
String arrayTypeDescriptor = this.arrayTypeDescriptor;
250+
Assert.state(exitTypeDescriptor != null && arrayTypeDescriptor != null,
251+
"Array not compilable without descriptors");
252+
CodeFlow.insertCheckCast(mv, arrayTypeDescriptor);
253+
int insn = switch (arrayTypeDescriptor) {
254+
case "[D" -> DALOAD;
255+
case "[F" -> FALOAD;
256+
case "[J" -> LALOAD;
257+
case "[I" -> IALOAD;
258+
case "[S" -> SALOAD;
259+
case "[B", "[Z" -> BALOAD; // byte[] & boolean[] are both loaded via BALOAD
260+
case "[C" -> CALOAD;
261+
default -> AALOAD;
287262
};
288263

289264
cf.enterCompilationScope();
@@ -329,7 +304,7 @@ else if (this.indexedType == IndexedType.OBJECT) {
329304
compilablePropertyAccessor.generateCode(propertyName, mv, cf);
330305
}
331306

332-
cf.pushDescriptor(this.exitTypeDescriptor);
307+
cf.pushDescriptor(exitTypeDescriptor);
333308
}
334309

335310
@Override
@@ -394,55 +369,64 @@ private Object accessArrayElement(Object ctx, int idx) throws SpelEvaluationExce
394369
boolean[] array = (boolean[]) ctx;
395370
checkAccess(array.length, idx);
396371
this.exitTypeDescriptor = "Z";
372+
this.arrayTypeDescriptor = "[Z";
397373
return array[idx];
398374
}
399375
else if (arrayComponentType == byte.class) {
400376
byte[] array = (byte[]) ctx;
401377
checkAccess(array.length, idx);
402378
this.exitTypeDescriptor = "B";
379+
this.arrayTypeDescriptor = "[B";
403380
return array[idx];
404381
}
405382
else if (arrayComponentType == char.class) {
406383
char[] array = (char[]) ctx;
407384
checkAccess(array.length, idx);
408385
this.exitTypeDescriptor = "C";
386+
this.arrayTypeDescriptor = "[C";
409387
return array[idx];
410388
}
411389
else if (arrayComponentType == double.class) {
412390
double[] array = (double[]) ctx;
413391
checkAccess(array.length, idx);
414392
this.exitTypeDescriptor = "D";
393+
this.arrayTypeDescriptor = "[D";
415394
return array[idx];
416395
}
417396
else if (arrayComponentType == float.class) {
418397
float[] array = (float[]) ctx;
419398
checkAccess(array.length, idx);
420399
this.exitTypeDescriptor = "F";
400+
this.arrayTypeDescriptor = "[F";
421401
return array[idx];
422402
}
423403
else if (arrayComponentType == int.class) {
424404
int[] array = (int[]) ctx;
425405
checkAccess(array.length, idx);
426406
this.exitTypeDescriptor = "I";
407+
this.arrayTypeDescriptor = "[I";
427408
return array[idx];
428409
}
429410
else if (arrayComponentType == long.class) {
430411
long[] array = (long[]) ctx;
431412
checkAccess(array.length, idx);
432413
this.exitTypeDescriptor = "J";
414+
this.arrayTypeDescriptor = "[J";
433415
return array[idx];
434416
}
435417
else if (arrayComponentType == short.class) {
436418
short[] array = (short[]) ctx;
437419
checkAccess(array.length, idx);
438420
this.exitTypeDescriptor = "S";
421+
this.arrayTypeDescriptor = "[S";
439422
return array[idx];
440423
}
441424
else {
442425
Object[] array = (Object[]) ctx;
443426
checkAccess(array.length, idx);
444427
Object retValue = array[idx];
445428
this.exitTypeDescriptor = CodeFlow.toDescriptor(arrayComponentType);
429+
this.arrayTypeDescriptor = CodeFlow.toDescriptor(array.getClass());
446430
return retValue;
447431
}
448432
}

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

+12
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,18 @@ void indexIntoMapOfPrimitiveIntArrayWithCompilableMapAccessor() {
652652
assertThat(getAst().getExitDescriptor()).isEqualTo("I");
653653
}
654654

655+
@Test
656+
void indexIntoSetCannotBeCompiled() {
657+
Set<Integer> set = Set.of(42);
658+
659+
expression = parser.parseExpression("[0]");
660+
661+
assertThat(expression.getValue(set)).isEqualTo(42);
662+
assertCannotCompile(expression);
663+
assertThat(expression.getValue(set)).isEqualTo(42);
664+
assertThat(getAst().getExitDescriptor()).isNull();
665+
}
666+
655667
@Test
656668
void indexIntoStringCannotBeCompiled() {
657669
String text = "enigma";

0 commit comments

Comments
 (0)