Browse Source

Support compilation of array and list indexing with Integer in SpEL

Prior to this commit, the Spring Expression Language (SpEL) failed to
compile an expression that indexed into any array or list using an
Integer.

This commit adds support for compilation of such expressions by
ensuring that an Integer is unboxed into an int in the compiled
bytecode.

See gh-32694
Closes gh-32908
pull/33047/head
Sam Brannen 2 years ago
parent
commit
cda577d1aa
  1. 20
      spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java
  2. 26
      spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java

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

@ -221,6 +221,8 @@ public class Indexer extends SpelNodeImpl {
cf.loadTarget(mv); cf.loadTarget(mv);
} }
SpelNodeImpl index = this.children[0];
if (this.indexedType == IndexedType.ARRAY) { if (this.indexedType == IndexedType.ARRAY) {
String exitTypeDescriptor = this.exitTypeDescriptor; String exitTypeDescriptor = this.exitTypeDescriptor;
Assert.state(exitTypeDescriptor != null, "Array not compilable without descriptor"); Assert.state(exitTypeDescriptor != null, "Array not compilable without descriptor");
@ -266,18 +268,13 @@ public class Indexer extends SpelNodeImpl {
} }
}; };
SpelNodeImpl index = this.children[0]; generateIndexCode(mv, cf, index, int.class);
cf.enterCompilationScope();
index.generateCode(mv, cf);
cf.exitCompilationScope();
mv.visitInsn(insn); mv.visitInsn(insn);
} }
else if (this.indexedType == IndexedType.LIST) { else if (this.indexedType == IndexedType.LIST) {
mv.visitTypeInsn(CHECKCAST, "java/util/List"); mv.visitTypeInsn(CHECKCAST, "java/util/List");
cf.enterCompilationScope(); generateIndexCode(mv, cf, index, int.class);
this.children[0].generateCode(mv, cf);
cf.exitCompilationScope();
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "get", "(I)Ljava/lang/Object;", true); mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "get", "(I)Ljava/lang/Object;", true);
} }
@ -285,13 +282,13 @@ public class Indexer extends SpelNodeImpl {
mv.visitTypeInsn(CHECKCAST, "java/util/Map"); mv.visitTypeInsn(CHECKCAST, "java/util/Map");
// Special case when the key is an unquoted string literal that will be parsed as // Special case when the key is an unquoted string literal that will be parsed as
// a property/field reference // a property/field reference
if ((this.children[0] instanceof PropertyOrFieldReference reference)) { if ((index instanceof PropertyOrFieldReference reference)) {
String mapKeyName = reference.getName(); String mapKeyName = reference.getName();
mv.visitLdcInsn(mapKeyName); mv.visitLdcInsn(mapKeyName);
} }
else { else {
cf.enterCompilationScope(); cf.enterCompilationScope();
this.children[0].generateCode(mv, cf); index.generateCode(mv, cf);
cf.exitCompilationScope(); cf.exitCompilationScope();
} }
mv.visitMethodInsn( mv.visitMethodInsn(
@ -328,6 +325,11 @@ public class Indexer extends SpelNodeImpl {
cf.pushDescriptor(this.exitTypeDescriptor); cf.pushDescriptor(this.exitTypeDescriptor);
} }
private void generateIndexCode(MethodVisitor mv, CodeFlow cf, SpelNodeImpl indexNode, Class<?> indexType) {
String indexDesc = CodeFlow.toDescriptor(indexType);
generateCodeForArgument(mv, cf, indexNode, indexDesc);
}
@Override @Override
public String toStringAST() { public String toStringAST() {
return "[" + getChild(0).toStringAST() + "]"; return "[" + getChild(0).toStringAST() + "]";

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

@ -671,6 +671,32 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String"); assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
} }
@Test // gh-32694, gh-32908
void indexIntoArrayUsingIntegerWrapper() {
context.setVariable("array", new int[] {1, 2, 3, 4});
context.setVariable("index", 2);
expression = parser.parseExpression("#array[#index]");
assertThat(expression.getValue(context)).isEqualTo(3);
assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(3);
assertThat(getAst().getExitDescriptor()).isEqualTo("I");
}
@Test // gh-32694, gh-32908
void indexIntoListUsingIntegerWrapper() {
context.setVariable("list", List.of(1, 2, 3, 4));
context.setVariable("index", 2);
expression = parser.parseExpression("#list[#index]");
assertThat(expression.getValue(context)).isEqualTo(3);
assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(3);
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
}
private String stringify(Object object) { private String stringify(Object object) {
Stream<? extends Object> stream; Stream<? extends Object> stream;
if (object instanceof Collection<?> collection) { if (object instanceof Collection<?> collection) {

Loading…
Cancel
Save