From 8c0fefc9fa1348791a729ee3604b3c3ebf821246 Mon Sep 17 00:00:00 2001 From: Jialin Chen Date: Sat, 24 Oct 2026 01:57:38 +0800 Subject: [PATCH] Fix compiled SpEL > and >= comparisons with NaN Prior to this commit, a compiled SpEL expression that compared double or float operands with the > or >= operator evaluated to true if one of the operands was NaN, whereas the same expression evaluated to false in interpreted mode, as it does in Java. For example, `1.0d > T(Double).NaN` was false when interpreted but true once compiled. The generated bytecode always used DCMPG/FCMPG, which push 1 if either operand is NaN, so the subsequent IFLE/IFLT jump to the false branch was never taken for > and >=. This commit uses DCMPL/FCMPL for the > and >= operators, as javac does, so that comparisons with NaN evaluate to false in compiled mode as well. Signed-off-by: Jialin Chen --- .../expression/spel/ast/Operator.java | 7 +++++-- .../spel/SpelCompilationCoverageTests.java | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Operator.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Operator.java index 56dfe050b796..28c854db5df7 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Operator.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Operator.java @@ -222,12 +222,15 @@ protected void generateComparisonCode(MethodVisitor mv, CodeFlow cf, int compIns } // assert: SpelCompiler.boxingCompatible(leftDesc, rightDesc) + // For OpGT and OpGE, use DCMPL/FCMPL (NaN yields -1) like javac, so that + // a comparison with NaN is false; DCMPG/FCMPG (NaN yields 1) otherwise. + boolean greaterThan = (compInstruction1 == IFLE || compInstruction1 == IFLT); if (targetType == 'D') { - mv.visitInsn(DCMPG); + mv.visitInsn(greaterThan ? DCMPL : DCMPG); mv.visitJumpInsn(compInstruction1, elseTarget); } else if (targetType == 'F') { - mv.visitInsn(FCMPG); + mv.visitInsn(greaterThan ? FCMPL : FCMPG); mv.visitJumpInsn(compInstruction1, elseTarget); } else if (targetType == 'J') { 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..7a59238dc6a0 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 @@ -962,6 +962,20 @@ void opGe() { assertThat((Boolean) expression.getValue()).isTrue(); } + @ParameterizedTest + @ValueSource(strings = { + "T(Double).NaN > 1.0d", "1.0d > T(Double).NaN", "T(Double).NaN >= 1.0d", "1.0d >= T(Double).NaN", + "T(Double).NaN < 1.0d", "1.0d < T(Double).NaN", "T(Double).NaN <= 1.0d", "1.0d <= T(Double).NaN", + "T(Float).NaN > 1.0f", "1.0f > T(Float).NaN", "T(Float).NaN >= 1.0f", "1.0f >= T(Float).NaN", + "T(Float).NaN < 1.0f", "1.0f < T(Float).NaN", "T(Float).NaN <= 1.0f", "1.0f <= T(Float).NaN" + }) + void relationalOperatorsWithNaN(String expressionString) { + expression = parse(expressionString); + assertThat((Boolean) expression.getValue()).isFalse(); + assertCanCompile(expression); + assertThat((Boolean) expression.getValue()).isFalse(); + } + @Test void opEq() { String tvar = "35";