From 75c26f8e2c9afef4573fa48e77f56f687096172b Mon Sep 17 00:00:00 2001 From: Jialin Chen Date: Sat, 24 Oct 2026 02:07:54 +0800 Subject: [PATCH] Fix compilation of SpEL Elvis with primitive operands Prior to this commit, compiling a SpEL Elvis expression whose operands have the same primitive type, such as `count ?: 0` for an int property or `3.5d ?: 4.5d`, failed with a VerifyError, reported as "Failed to instantiate CompiledExpression". In IMMEDIATE compiler mode, every evaluation after the first one therefore failed, whereas MIXED mode fell back to interpreted mode. The generated code boxed the left-hand value for the null check but did not box the right-hand value, since the exit type descriptor was primitive, so the two branches left different types on the stack. Since a primitive value is neither null nor an empty String, this commit only generates the code for the left-hand operand in that case, boxing its value if the exit type descriptor is not primitive. Signed-off-by: Jialin Chen --- .../expression/spel/ast/Elvis.java | 13 +++++-- .../spel/SpelCompilationCoverageTests.java | 35 +++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java index b2f65812d3df..d63dd5972662 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java @@ -128,12 +128,19 @@ public void generateCode(MethodVisitor mv, CodeFlow cf) { this.children[0].generateCode(mv, cf); String lastDesc = cf.lastDescriptor(); Assert.state(lastDesc != null, "No last descriptor"); + if (CodeFlow.isPrimitive(lastDesc) && !"V".equals(lastDesc)) { + // A primitive value is neither null nor an empty String, so the value + // of the expression is always the value of the left-hand operand. + if (!CodeFlow.isPrimitive(this.exitTypeDescriptor)) { + CodeFlow.insertBoxIfNecessary(mv, lastDesc.charAt(0)); + } + cf.exitCompilationScope(); + cf.pushDescriptor(this.exitTypeDescriptor); + return; + } if ("Ljava/util/Optional".equals(lastDesc)) { CodeFlow.insertOptionalUnwrapIfNecessary(mv, lastDesc); } - else { - CodeFlow.insertBoxIfNecessary(mv, lastDesc.charAt(0)); - } cf.exitCompilationScope(); mv.visitInsn(DUP); 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 2f41db9c0b94..1873c7661a49 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 @@ -6758,6 +6758,41 @@ void elvisMiscellaneous() { assertIsCompiled(exp); } + @Test + void elvisWithPrimitiveOperandsOfSameType() { + expression = parser.parseExpression("3 ?: 4"); + assertThat(expression.getValue()).isEqualTo(3); + assertCanCompile(expression); + assertThat(expression.getValue()).isEqualTo(3); + assertThat(getAst().getExitDescriptor()).isEqualTo("I"); + + // double slot primitive + expression = parser.parseExpression("3L ?: 4L"); + assertThat(expression.getValue()).isEqualTo(3L); + assertCanCompile(expression); + assertThat(expression.getValue()).isEqualTo(3L); + assertThat(getAst().getExitDescriptor()).isEqualTo("J"); + + expression = parser.parseExpression("3.5d ?: 4.5d"); + assertThat(expression.getValue()).isEqualTo(3.5d); + assertCanCompile(expression); + assertThat(expression.getValue()).isEqualTo(3.5d); + assertThat(getAst().getExitDescriptor()).isEqualTo("D"); + + expression = parser.parseExpression("true ?: false"); + assertThat(expression.getValue()).isEqualTo(true); + assertCanCompile(expression); + assertThat(expression.getValue()).isEqualTo(true); + assertThat(getAst().getExitDescriptor()).isEqualTo("Z"); + + // method returning primitive + expression = parser.parseExpression("length() ?: 0"); + assertThat(expression.getValue("abc")).isEqualTo(3); + assertCanCompile(expression); + assertThat(expression.getValue("abc")).isEqualTo(3); + assertThat(getAst().getExitDescriptor()).isEqualTo("I"); + } + @Test void elvis_SPR17214() { SpelParserConfiguration spc = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build();