Skip to content

Commit 4542b53

Browse files
committed
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-30149
1 parent 52c93b1 commit 4542b53

File tree

3 files changed

+47
-13
lines changed

3 files changed

+47
-13
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -260,7 +260,11 @@ public enum SpelMessage {
260260

261261
/** @since 5.2.20 */
262262
MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED(Kind.ERROR, 1075,
263-
"Array declares too many elements, exceeding the threshold of ''{0}''");
263+
"Array declares too many elements, exceeding the threshold of ''{0}''"),
264+
265+
/** @since 5.2.23 */
266+
MAX_REPEATED_TEXT_SIZE_EXCEEDED(Kind.ERROR, 1076,
267+
"Repeated text results in too many characters, exceeding the threshold of ''{0}''");
264268

265269

266270
private final Kind kind;

spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java

+24-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,6 +25,8 @@
2525
import org.springframework.expression.TypedValue;
2626
import org.springframework.expression.spel.CodeFlow;
2727
import org.springframework.expression.spel.ExpressionState;
28+
import org.springframework.expression.spel.SpelEvaluationException;
29+
import org.springframework.expression.spel.SpelMessage;
2830
import org.springframework.util.Assert;
2931
import org.springframework.util.NumberUtils;
3032

@@ -52,6 +54,13 @@
5254
*/
5355
public class OpMultiply extends Operator {
5456

57+
/**
58+
* Maximum number of characters permitted in repeated text.
59+
* @since 5.2.23
60+
*/
61+
private static final int MAX_REPEATED_TEXT_SIZE = 256;
62+
63+
5564
public OpMultiply(int startPos, int endPos, SpelNodeImpl... operands) {
5665
super("*", startPos, endPos, operands);
5766
}
@@ -109,17 +118,27 @@ else if (CodeFlow.isIntegerForNumericOp(leftNumber) || CodeFlow.isIntegerForNume
109118
}
110119

111120
if (leftOperand instanceof String && rightOperand instanceof Integer) {
112-
int repeats = (Integer) rightOperand;
113-
StringBuilder result = new StringBuilder();
114-
for (int i = 0; i < repeats; i++) {
115-
result.append(leftOperand);
121+
String text = (String) leftOperand;
122+
int count = (Integer) rightOperand;
123+
int requestedSize = text.length() * count;
124+
checkRepeatedTextSize(requestedSize);
125+
StringBuilder result = new StringBuilder(requestedSize);
126+
for (int i = 0; i < count; i++) {
127+
result.append(text);
116128
}
117129
return new TypedValue(result.toString());
118130
}
119131

120132
return state.operate(Operation.MULTIPLY, leftOperand, rightOperand);
121133
}
122134

135+
private void checkRepeatedTextSize(int requestedSize) {
136+
if (requestedSize > MAX_REPEATED_TEXT_SIZE) {
137+
throw new SpelEvaluationException(getStartPosition(),
138+
SpelMessage.MAX_REPEATED_TEXT_SIZE_EXCEEDED, MAX_REPEATED_TEXT_SIZE);
139+
}
140+
}
141+
123142
@Override
124143
public boolean isCompilable() {
125144
if (!getLeftOperand().isCompilable()) {

spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java

+17-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,17 +21,20 @@
2121

2222
import org.junit.jupiter.api.Test;
2323

24+
import org.springframework.expression.Expression;
2425
import org.springframework.expression.spel.ast.Operator;
2526
import org.springframework.expression.spel.standard.SpelExpression;
2627

2728
import static org.assertj.core.api.Assertions.assertThat;
29+
import static org.springframework.expression.spel.SpelMessage.MAX_REPEATED_TEXT_SIZE_EXCEEDED;
2830

2931
/**
3032
* Tests the evaluation of expressions using relational operators.
3133
*
3234
* @author Andy Clement
3335
* @author Juergen Hoeller
3436
* @author Giovanni Dall'Oglio Risso
37+
* @author Sam Brannen
3538
*/
3639
public class OperatorTests extends AbstractExpressionTests {
3740

@@ -324,11 +327,6 @@ public void testRealLiteral() {
324327
evaluate("3.5", 3.5d, Double.class);
325328
}
326329

327-
@Test
328-
public void testMultiplyStringInt() {
329-
evaluate("'a' * 5", "aaaaa", String.class);
330-
}
331-
332330
@Test
333331
public void testMultiplyDoubleDoubleGivesDouble() {
334332
evaluate("3.0d * 5.0d", 15.0d, Double.class);
@@ -576,6 +574,19 @@ public void testStrings() {
576574
evaluate("'abc' != 'def'", true, Boolean.class);
577575
}
578576

577+
@Test
578+
void stringRepeat() {
579+
evaluate("'abc' * 0", "", String.class);
580+
evaluate("'abc' * 1", "abc", String.class);
581+
evaluate("'abc' * 2", "abcabc", String.class);
582+
583+
Expression expr = parser.parseExpression("'a' * 256");
584+
assertThat(expr.getValue(context, String.class)).hasSize(256);
585+
586+
// 4 is the position of the '*' (repeat operator)
587+
evaluateAndCheckError("'a' * 257", String.class, MAX_REPEATED_TEXT_SIZE_EXCEEDED, 4);
588+
}
589+
579590
@Test
580591
public void testLongs() {
581592
evaluate("3L == 4L", false, Boolean.class);

0 commit comments

Comments
 (0)