diff --git a/framework-docs/modules/ROOT/pages/appendix.adoc b/framework-docs/modules/ROOT/pages/appendix.adoc index 8c2cbd703ff..9107fe9c5a2 100644 --- a/framework-docs/modules/ROOT/pages/appendix.adoc +++ b/framework-docs/modules/ROOT/pages/appendix.adoc @@ -74,6 +74,12 @@ expressions used in XML bean definitions, `@Value`, etc. | The mode to use when compiling expressions for the xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language]. +| `spring.expression.maxBigPowerBits` +| The default maximum number of bits permitted in the result of a `BigDecimal` or +`BigInteger` power operation within a +xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language] +expression. + | `spring.expression.maxOperations` | The default maximum number of operations permitted during xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language] diff --git a/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc b/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc index 80a1a85d663..e8d22854a0c 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc @@ -574,6 +574,19 @@ property or Spring property named `spring.expression.maxOperations` to the maxim of operations required by your application (see xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]). +In addition, the result of a `BigDecimal` or `BigInteger` power operation within a SpEL +expression cannot exceed 1,000,000 bits by default – approximately equivalent to a +decimal number with 300,000 digits. Power operations involving large base values or large +exponents can be computationally expensive, and this limit ensures that evaluations +remain bounded; however, the `maximumBigPowerBits` value is configurable. If you create a +`SpelExpressionParser` programmatically (the recommended approach), you can specify a +custom `maximumBigPowerBits` value when creating the `SpelParserConfiguration` that you +provide to the `SpelExpressionParser`. To remove this limit entirely, pass +`Integer.MAX_VALUE` as the `maximumBigPowerBits` value. If you are not able to configure +an explicit value for `maximumBigPowerBits` via `SpelParserConfiguration`, you can set a +JVM system property or Spring property named `spring.expression.maxBigPowerBits` to the +maximum result size in bits (see xref:appendix.adoc#appendix-spring-properties[Supported +Spring Properties]). [[expressions-spel-compilation]] == SpEL Compilation diff --git a/spring-core/src/main/java/org/springframework/core/SpringProperties.java b/spring-core/src/main/java/org/springframework/core/SpringProperties.java index bcdc21dd127..57e495dd882 100644 --- a/spring-core/src/main/java/org/springframework/core/SpringProperties.java +++ b/spring-core/src/main/java/org/springframework/core/SpringProperties.java @@ -44,6 +44,7 @@ import org.jspecify.annotations.Nullable; * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#STRICT_LOCKING_PROPERTY_NAME * @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME * @see org.springframework.expression.spel.SpelParserConfiguration#SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME + * @see org.springframework.expression.spel.SpelParserConfiguration#SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME * @see org.springframework.expression.spel.SpelParserConfiguration#SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME * @see org.springframework.jdbc.core.StatementCreatorUtils#IGNORE_GETPARAMETERTYPE_PROPERTY_NAME * @see org.springframework.jndi.JndiLocatorDelegate#IGNORE_JNDI_PROPERTY_NAME diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java index d58c89e7aea..28cea823c73 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java @@ -307,7 +307,12 @@ public enum SpelMessage { /** @since 6.2.19 */ MAX_OPERATIONS_EXCEEDED(Kind.ERROR, 1085, - "SpEL expression evaluation exceeded the threshold of ''{0}'' operations"); + "SpEL expression evaluation exceeded the threshold of ''{0}'' operations"), + + /** @since 7.0.9 */ + MAX_BIG_POWER_RESULT_EXCEEDED(Kind.ERROR, 1086, + "BigDecimal/BigInteger power operation with base bit length ''{0}'' and exponent ''{1}'' " + + "would produce a result exceeding the configured maximum of ''{2}'' bits"); private final Kind kind; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java index aa2a3db8c74..73e218ec5f8 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java @@ -49,6 +49,16 @@ public class SpelParserConfiguration { */ public static final int DEFAULT_MAX_OPERATIONS = 10_000; + /** + * Default maximum number of bits permitted in the result of a + * {@link java.math.BigDecimal} or {@link java.math.BigInteger} power operation + * within a SpEL expression: {@value}. + *
Approximately equivalent to a decimal number with 300,000 digits. + * @since 7.0.9 + * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME + */ + public static final int DEFAULT_MAX_BIG_POWER_BITS = 1_000_000; + /** * System property to configure the default compiler mode for SpEL expression parsers: {@value}. *
NOTE: Instead of relying on a global default, applications @@ -65,7 +75,7 @@ public class SpelParserConfiguration { * during SpEL expression evaluation: {@value}. *
NOTE: Instead of relying on a global default, applications * and frameworks should ideally set an explicit custom value via the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)} + * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} * constructor which provides complete configuration control and the ability * to override global defaults per use case. *
Can also be configured via the {@link SpringProperties} mechanism. @@ -74,6 +84,22 @@ public class SpelParserConfiguration { */ public static final String SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME = "spring.expression.maxOperations"; + /** + * System property to configure the default maximum number of bits permitted in the + * result of a {@link java.math.BigDecimal} or {@link java.math.BigInteger} power + * operation within a SpEL expression: {@value}. + *
NOTE: Instead of relying on a global default, applications + * and frameworks should ideally set an explicit custom value via the + * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} + * constructor which provides complete configuration control and the ability + * to override global defaults per use case. + *
Can also be configured via the {@link SpringProperties} mechanism. + * @since 7.0.9 + * @see #DEFAULT_MAX_BIG_POWER_BITS + */ + public static final String SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME = + "spring.expression.maxBigPowerBits"; + private static final SpelCompilerMode defaultCompilerMode; @@ -98,15 +124,18 @@ public class SpelParserConfiguration { private final int maximumOperations; + private final int maximumBigPowerBits; + /** * Create a new {@code SpelParserConfiguration} instance with default settings. *
NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)} + * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} * constructor for complete configuration control and the ability to override * global defaults per use case. * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME + * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME */ public SpelParserConfiguration() { this(null, null, false, false, Integer.MAX_VALUE); @@ -115,7 +144,7 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. *
NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)} + * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} * constructor for complete configuration control and the ability to override * global defaults per use case. * @param compilerMode the compiler mode that parsers using this configuration @@ -124,6 +153,7 @@ public class SpelParserConfiguration { * expression compilation; or {@code null} to use the default {@code ClassLoader} * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME + * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME */ public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader) { this(compilerMode, compilerClassLoader, false, false, Integer.MAX_VALUE); @@ -132,13 +162,14 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. *
NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)} + * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} * constructor for complete configuration control and the ability to override * global defaults per use case. * @param autoGrowNullReferences if null references should automatically grow * @param autoGrowCollections if collections should automatically grow * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME + * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME */ public SpelParserConfiguration(boolean autoGrowNullReferences, boolean autoGrowCollections) { this(null, null, autoGrowNullReferences, autoGrowCollections, Integer.MAX_VALUE); @@ -147,7 +178,7 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. *
NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)} + * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} * constructor for complete configuration control and the ability to override * global defaults per use case. * @param autoGrowNullReferences if null references should automatically grow @@ -155,6 +186,7 @@ public class SpelParserConfiguration { * @param maximumAutoGrowSize the maximum size to which a collection can auto grow * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME + * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME */ public SpelParserConfiguration(boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize) { this(null, null, autoGrowNullReferences, autoGrowCollections, maximumAutoGrowSize); @@ -163,7 +195,7 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. *
NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)} + * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} * constructor for complete configuration control and the ability to override * global defaults per use case. * @param compilerMode the compiler mode that parsers using this configuration @@ -175,6 +207,7 @@ public class SpelParserConfiguration { * @param maximumAutoGrowSize the maximum size to which a collection can auto grow * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME + * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME */ public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader, boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize) { @@ -186,7 +219,7 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. *
NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)} + * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} * constructor for complete configuration control and the ability to override * global defaults per use case. * @param compilerMode the compiler mode that parsers using this configuration @@ -201,6 +234,7 @@ public class SpelParserConfiguration { * @since 5.2.25 * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME + * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME */ public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader, boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize, int maximumExpressionLength) { @@ -209,6 +243,34 @@ public class SpelParserConfiguration { autoGrowCollections, maximumAutoGrowSize, maximumExpressionLength, retrieveMaxOperations()); } + /** + * Create a new {@code SpelParserConfiguration} instance. + *
NOTE: Favor the + * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} + * constructor for complete configuration control and the ability to override + * global defaults per use case. + * @param compilerMode the compiler mode that parsers using this configuration + * should use; must not be {@code null} + * @param compilerClassLoader the {@code ClassLoader} to use as the basis for + * expression compilation; or {@code null} to use the default {@code ClassLoader} + * @param autoGrowNullReferences if null references should automatically grow + * @param autoGrowCollections if collections should automatically grow + * @param maximumAutoGrowSize the maximum size to which a collection can auto grow + * @param maximumExpressionLength the maximum length of a SpEL expression; + * must be a positive number + * @param maximumOperations the maximum number of operations permitted during + * SpEL expression evaluation; must be a positive number + * @since 6.2.19 + * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME + */ + public SpelParserConfiguration(SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader, + boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize, int maximumExpressionLength, + int maximumOperations) { + + this(compilerMode, compilerClassLoader, autoGrowNullReferences, autoGrowCollections, + maximumAutoGrowSize, maximumExpressionLength, maximumOperations, retrieveMaxBigPowerBits()); + } + /** * Create a new {@code SpelParserConfiguration} instance. * @param compilerMode the compiler mode that parsers using this configuration @@ -222,15 +284,19 @@ public class SpelParserConfiguration { * must be a positive number * @param maximumOperations the maximum number of operations permitted during * SpEL expression evaluation; must be a positive number - * @since 6.2.19 + * @param maximumBigPowerBits the maximum number of bits permitted in the + * result of a {@link java.math.BigDecimal} or {@link java.math.BigInteger} power + * operation; must be a positive number; use {@link Integer#MAX_VALUE} for no limit + * @since 7.0.9 */ public SpelParserConfiguration(SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader, boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize, int maximumExpressionLength, - int maximumOperations) { + int maximumOperations, int maximumBigPowerBits) { Assert.notNull(compilerMode, "'compilerMode' must not be null"); Assert.isTrue(maximumExpressionLength > 0, "'maximumExpressionLength' must be a positive number"); Assert.isTrue(maximumOperations > 0, "'maximumOperations' must be a positive number"); + Assert.isTrue(maximumBigPowerBits > 0, "'maximumBigPowerBits' must be a positive number"); this.compilerMode = compilerMode; this.compilerClassLoader = compilerClassLoader; @@ -239,6 +305,7 @@ public class SpelParserConfiguration { this.maximumAutoGrowSize = maximumAutoGrowSize; this.maximumExpressionLength = maximumExpressionLength; this.maximumOperations = maximumOperations; + this.maximumBigPowerBits = maximumBigPowerBits; } @@ -294,6 +361,15 @@ public class SpelParserConfiguration { return this.maximumOperations; } + /** + * Return the maximum number of bits permitted in the result of a + * {@link java.math.BigDecimal} or {@link java.math.BigInteger} power operation. + * @since 7.0.9 + */ + public int getMaximumBigPowerBits() { + return this.maximumBigPowerBits; + } + private static int retrieveMaxOperations() { String value = SpringProperties.getProperty(SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME); @@ -313,4 +389,21 @@ public class SpelParserConfiguration { } } + private static int retrieveMaxBigPowerBits() { + String value = SpringProperties.getProperty(SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME); + if (!StringUtils.hasText(value)) { + return DEFAULT_MAX_BIG_POWER_BITS; + } + try { + int maxBits = Integer.parseInt(value.trim()); + Assert.isTrue(maxBits > 0, () -> "Value [" + maxBits + "] for system property [" + + SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME + "] must be positive"); + return maxBits; + } + catch (NumberFormatException ex) { + throw new IllegalArgumentException("Failed to parse value for system property [" + + SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME + "]: " + ex.getMessage(), ex); + } + } + } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorPower.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorPower.java index 1891db890f6..ac7e4eeadce 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorPower.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorPower.java @@ -23,13 +23,15 @@ import org.springframework.expression.EvaluationException; import org.springframework.expression.Operation; import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; -import org.springframework.util.NumberUtils; +import org.springframework.expression.spel.SpelEvaluationException; +import org.springframework.expression.spel.SpelMessage; /** * The power operator. * * @author Andy Clement * @author Giovanni Dall'Oglio Risso + * @author Sam Brannen * @since 3.0 */ public class OperatorPower extends Operator { @@ -41,21 +43,20 @@ public class OperatorPower extends Operator { @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { - SpelNodeImpl leftOp = getLeftOperand(); - SpelNodeImpl rightOp = getRightOperand(); - - Object leftOperand = leftOp.getValueInternal(state).getValue(); - Object rightOperand = rightOp.getValueInternal(state).getValue(); + Object leftOperand = getLeftOperand().getValueInternal(state).getValue(); + Object rightOperand = getRightOperand().getValueInternal(state).getValue(); if (leftOperand instanceof Number leftNumber && rightOperand instanceof Number rightNumber) { state.trackOperation(); - if (leftNumber instanceof BigDecimal) { - BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class); - return new TypedValue(leftBigDecimal.pow(rightNumber.intValue())); + if (leftNumber instanceof BigDecimal leftBigDecimal) { + int exponent = rightNumber.intValue(); + checkBigNumberPowerBits(state, leftBigDecimal.unscaledValue().bitLength(), exponent); + return new TypedValue(leftBigDecimal.pow(exponent)); } - else if (leftNumber instanceof BigInteger) { - BigInteger leftBigInteger = NumberUtils.convertNumberToTargetClass(leftNumber, BigInteger.class); - return new TypedValue(leftBigInteger.pow(rightNumber.intValue())); + else if (leftNumber instanceof BigInteger leftBigInteger) { + int exponent = rightNumber.intValue(); + checkBigNumberPowerBits(state, leftBigInteger.bitLength(), exponent); + return new TypedValue(leftBigInteger.pow(exponent)); } else if (leftNumber instanceof Double || rightNumber instanceof Double) { return new TypedValue(Math.pow(leftNumber.doubleValue(), rightNumber.doubleValue())); @@ -76,4 +77,13 @@ public class OperatorPower extends Operator { return state.operate(Operation.POWER, leftOperand, rightOperand); } + private void checkBigNumberPowerBits(ExpressionState state, int baseBitLength, int exponent) { + int maxBigPowerBits = state.getConfiguration().getMaximumBigPowerBits(); + long estimatedBigPowerBits = (long) baseBitLength * exponent; + if (estimatedBigPowerBits > maxBigPowerBits) { + throw new SpelEvaluationException(getStartPosition(), SpelMessage.MAX_BIG_POWER_RESULT_EXCEEDED, + baseBitLength, exponent, maxBigPowerBits); + } + } + } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java index 31a248a08b7..721ed9fd81e 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/AbstractExpressionTests.java @@ -19,6 +19,7 @@ package org.springframework.expression.spel; import java.util.Arrays; import java.util.List; +import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -168,6 +169,23 @@ public abstract class AbstractExpressionTests { evaluateAndCheckError(this.parser, expression, expectedReturnType, expectedMessage, otherProperties); } + /** + * Evaluate the specified expression and ensure the expected message comes out. + * The message may have inserts and they will be checked if otherProperties is specified. + * The first entry in otherProperties should always be the position. + * @param evaluationContext the evaluation context to use + * @param expression the expression to evaluate + * @param expectedReturnType ask the expression return value to be of this type if possible + * ({@code null} indicates don't ask for conversion) + * @param expectedMessage the expected message + * @param otherProperties the expected inserts within the message + */ + protected void evaluateAndCheckError(EvaluationContext evaluationContext, String expression, + Class> expectedReturnType, SpelMessage expectedMessage, Object... otherProperties) { + + evaluateAndCheckError(this.parser, evaluationContext, expression, expectedReturnType, expectedMessage, otherProperties); + } + /** * Evaluate the specified expression and ensure the expected message comes out. * The message may have inserts and they will be checked if otherProperties is specified. @@ -182,14 +200,32 @@ public abstract class AbstractExpressionTests { protected void evaluateAndCheckError(ExpressionParser parser, String expression, Class> expectedReturnType, SpelMessage expectedMessage, Object... otherProperties) { + evaluateAndCheckError(parser, this.context, expression, expectedReturnType, expectedMessage, otherProperties); + } + + /** + * Evaluate the specified expression and ensure the expected message comes out. + * The message may have inserts and they will be checked if otherProperties is specified. + * The first entry in otherProperties should always be the position. + * @param parser the expression parser to use + * @param evaluationContext the evaluation context to use + * @param expression the expression to evaluate + * @param expectedReturnType ask the expression return value to be of this type if possible + * ({@code null} indicates don't ask for conversion) + * @param expectedMessage the expected message + * @param otherProperties the expected inserts within the message + */ + protected void evaluateAndCheckError(ExpressionParser parser, EvaluationContext evaluationContext, + String expression, Class> expectedReturnType, SpelMessage expectedMessage, Object... otherProperties) { + assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> { Expression expr = parser.parseExpression(expression); assertThat(expr).as("expression").isNotNull(); if (expectedReturnType != null) { - expr.getValue(context, expectedReturnType); + expr.getValue(evaluationContext, expectedReturnType); } else { - expr.getValue(context); + expr.getValue(evaluationContext); } }).satisfies(ex -> { assertThat(ex.getMessageCode()).isEqualTo(expectedMessage); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index 06290373568..494cd7856ac 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -811,6 +811,77 @@ class EvaluationTests extends AbstractExpressionTests { } + @Nested + class PowerOperatorTests { + + private final EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); + + // Use a small limit (16 bits) to verify behavior in tests. + private static final int TEST_MAX_RESULT_BITS = 16; + + private final SpelExpressionParser limitedParser = new SpelExpressionParser( + new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, + 0, 10, 10, TEST_MAX_RESULT_BITS)); + + + @Test + void powerOperatorWithBigDecimal() { + context.setVariable("bd", BigDecimal.valueOf(2.0)); + Expression expr = parser.parseExpression("#bd ^ 4"); + assertThat(expr.getValue(context, BigDecimal.class)).isEqualByComparingTo("16"); + } + + @Test + void powerOperatorWithBigDecimalUnderResultLimit() { + // BigDecimal.valueOf(2.0).unscaledValue().bitLength() = 5 + // 5 * 3 = 15 bits <= TEST_MAX_RESULT_BITS (16) + context.setVariable("bd", BigDecimal.valueOf(2.0)); + Expression expr = limitedParser.parseExpression("#bd ^ 3"); + assertThat(expr.getValue(context, BigDecimal.class)).isEqualByComparingTo("8"); + } + + @Test + void powerOperatorWithBigDecimalExceedingResultLimit() { + // 5 * 4 = 20 bits > TEST_MAX_RESULT_BITS (16) + context.setVariable("bd", BigDecimal.valueOf(2.0)); + evaluateAndCheckError(limitedParser, context, "#bd ^ 4", BigDecimal.class, + SpelMessage.MAX_BIG_POWER_RESULT_EXCEEDED, + 4, // power operator position + 5, // base bit length + 4, // exponent + TEST_MAX_RESULT_BITS); + } + + @Test + void powerOperatorWithBigInteger() { + context.setVariable("bi", BigInteger.valueOf(2)); + Expression expr = parser.parseExpression("#bi ^ 4"); + assertThat(expr.getValue(context, BigInteger.class)).isEqualTo(BigInteger.valueOf(16)); + } + + @Test + void powerOperatorWithBigIntegerUnderResultLimit() { + // BigInteger.valueOf(2).bitLength() = 2 + // 2 * 8 = 16 bits == TEST_MAX_RESULT_BITS (16) + context.setVariable("bi", BigInteger.valueOf(2)); + Expression expr = limitedParser.parseExpression("#bi ^ 8"); + assertThat(expr.getValue(context, BigInteger.class)).isEqualTo(BigInteger.valueOf(256)); + } + + @Test + void powerOperatorWithBigIntegerExceedingResultLimit() { + // 2 * 9 = 18 bits > TEST_MAX_RESULT_BITS (16) + context.setVariable("bi", BigInteger.valueOf(2)); + evaluateAndCheckError(limitedParser, context, "#bi ^ 9", BigInteger.class, + SpelMessage.MAX_BIG_POWER_RESULT_EXCEEDED, + 4, // power operator position + 2, // base bit length + 9, // exponent + TEST_MAX_RESULT_BITS); + } + + } + @Nested class TernaryOperatorTests {