From cda577d1aacf0f90225f13050da42506f95bd2c8 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Tue, 23 Apr 2024 11:34:18 +0300 Subject: [PATCH] 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 --- .../expression/spel/ast/Indexer.java | 20 +++++++------- .../spel/SpelCompilationCoverageTests.java | 26 +++++++++++++++++++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index 2f6f8a922bd..e96c858c966 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -221,6 +221,8 @@ public class Indexer extends SpelNodeImpl { cf.loadTarget(mv); } + SpelNodeImpl index = this.children[0]; + if (this.indexedType == IndexedType.ARRAY) { String exitTypeDescriptor = this.exitTypeDescriptor; Assert.state(exitTypeDescriptor != null, "Array not compilable without descriptor"); @@ -266,18 +268,13 @@ public class Indexer extends SpelNodeImpl { } }; - SpelNodeImpl index = this.children[0]; - cf.enterCompilationScope(); - index.generateCode(mv, cf); - cf.exitCompilationScope(); + generateIndexCode(mv, cf, index, int.class); mv.visitInsn(insn); } else if (this.indexedType == IndexedType.LIST) { mv.visitTypeInsn(CHECKCAST, "java/util/List"); - cf.enterCompilationScope(); - this.children[0].generateCode(mv, cf); - cf.exitCompilationScope(); + generateIndexCode(mv, cf, index, int.class); 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"); // Special case when the key is an unquoted string literal that will be parsed as // a property/field reference - if ((this.children[0] instanceof PropertyOrFieldReference reference)) { + if ((index instanceof PropertyOrFieldReference reference)) { String mapKeyName = reference.getName(); mv.visitLdcInsn(mapKeyName); } else { cf.enterCompilationScope(); - this.children[0].generateCode(mv, cf); + index.generateCode(mv, cf); cf.exitCompilationScope(); } mv.visitMethodInsn( @@ -328,6 +325,11 @@ public class Indexer extends SpelNodeImpl { 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 public String toStringAST() { return "[" + getChild(0).toStringAST() + "]"; diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java index 51388a64087..4df4c4b3122 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java +++ b/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"); } + @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) { Stream stream; if (object instanceof Collection collection) {