Improve diagnostics in SpEL for repeated text

Attempting to create repeated text in a SpEL expression using the
repeat operator can result in errors that are not very helpful to the
user.

This commit improves the diagnostics in SpEL for the repeat operator by
throwing a SpelEvaluationException with a meaningful error message in
order to better assist the user.

Closes gh-30142
This commit is contained in:
Sam Brannen 2023-03-16 17:28:37 +01:00
parent 935c29e3dd
commit 5529294ec9
3 changed files with 41 additions and 10 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -264,7 +264,11 @@ public enum SpelMessage {
/** @since 5.3.17 */
MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED(Kind.ERROR, 1075,
"Array declares too many elements, exceeding the threshold of ''{0}''");
"Array declares too many elements, exceeding the threshold of ''{0}''"),
/** @since 5.3.26 */
MAX_REPEATED_TEXT_SIZE_EXCEEDED(Kind.ERROR, 1076,
"Repeated text results in too many characters, exceeding the threshold of ''{0}''");
private final Kind kind;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -25,6 +25,8 @@ import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;
@ -52,6 +54,13 @@ import org.springframework.util.NumberUtils;
*/
public class OpMultiply extends Operator {
/**
* Maximum number of characters permitted in repeated text.
* @since 5.3.26
*/
private static final int MAX_REPEATED_TEXT_SIZE = 256;
public OpMultiply(int startPos, int endPos, SpelNodeImpl... operands) {
super("*", startPos, endPos, operands);
}
@ -105,13 +114,21 @@ public class OpMultiply extends Operator {
}
}
if (leftOperand instanceof String text && rightOperand instanceof Integer repeats) {
return new TypedValue(text.repeat(repeats));
if (leftOperand instanceof String text && rightOperand instanceof Integer count) {
checkRepeatedTextSize(text, count);
return new TypedValue(text.repeat(count));
}
return state.operate(Operation.MULTIPLY, leftOperand, rightOperand);
}
private void checkRepeatedTextSize(String text, int count) {
if (text.length() * count > MAX_REPEATED_TEXT_SIZE) {
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.MAX_REPEATED_TEXT_SIZE_EXCEEDED, MAX_REPEATED_TEXT_SIZE);
}
}
@Override
public boolean isCompilable() {
if (!getLeftOperand().isCompilable()) {

View File

@ -21,10 +21,12 @@ import java.math.BigInteger;
import org.junit.jupiter.api.Test;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.ast.Operator;
import org.springframework.expression.spel.standard.SpelExpression;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.expression.spel.SpelMessage.MAX_REPEATED_TEXT_SIZE_EXCEEDED;
/**
* Tests the evaluation of expressions using various operators.
@ -326,11 +328,6 @@ class OperatorTests extends AbstractExpressionTests {
evaluate("3.5", 3.5d, Double.class);
}
@Test
void multiplyStringInt() {
evaluate("'a' * 5", "aaaaa", String.class);
}
@Test
void multiplyDoubleDoubleGivesDouble() {
evaluate("3.0d * 5.0d", 15.0d, Double.class);
@ -578,6 +575,19 @@ class OperatorTests extends AbstractExpressionTests {
evaluate("'abc' != 'def'", true, Boolean.class);
}
@Test
void stringRepeat() {
evaluate("'abc' * 0", "", String.class);
evaluate("'abc' * 1", "abc", String.class);
evaluate("'abc' * 2", "abcabc", String.class);
Expression expr = parser.parseExpression("'a' * 256");
assertThat(expr.getValue(context, String.class)).hasSize(256);
// 4 is the position of the '*' (repeat operator)
evaluateAndCheckError("'a' * 257", String.class, MAX_REPEATED_TEXT_SIZE_EXCEEDED, 4);
}
@Test
void longs() {
evaluate("3L == 4L", false, Boolean.class);