mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-25 00:09:02 +00:00
Limit result size of BigDecimal/BigInteger power operations in SpEL
This commit introduces a configurable limit on the estimated result size of BigDecimal and BigInteger power operations within SpEL expressions. The estimated result size in bits is computed as the product of the base value's bit length and the exponent. If this limit is exceeded, a SpelEvaluationException is thrown. The limit defaults to 1,000,000 bits, which is approximately equivalent to a decimal number with 300,000 digits, and can be configured either on a per-use-case basis via the new maximumBigPowerBits constructor argument in SpelParserConfiguration or globally as a JVM system property or Spring property named `spring.expression.maxBigPowerBits`. Parsers intended for trusted internal expressions may supply Integer.MAX_VALUE to remove the limit entirely. Closes ch-37034
This commit is contained in:
committed by
Brian Clozel
parent
ee1874ac52
commit
8a92c19e4d
+38
-2
@@ -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);
|
||||
|
||||
+71
@@ -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 {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user