From 908409e0b2057ef6a118ff848eed51e7897dfe7e Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 16 Mar 2023 17:28:37 +0100 Subject: [PATCH] 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 --- .../expression/spel/SpelMessage.java | 8 ++++-- .../expression/spel/ast/OpMultiply.java | 28 ++++++++++++++----- .../expression/spel/OperatorTests.java | 21 ++++++++++---- 3 files changed, 42 insertions(+), 15 deletions(-) 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 71ea50baab90..6aa413468170 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 @@ -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. @@ -260,7 +260,11 @@ public enum SpelMessage { /** @since 5.2.20 */ 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; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java index c9ae44eeeef0..fb6cc50a6d32 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2022 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.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.NumberUtils; /** @@ -51,6 +53,13 @@ */ 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 pos, SpelNodeImpl... operands) { super("*", pos, operands); } @@ -108,17 +117,22 @@ else if (CodeFlow.isIntegerForNumericOp(leftNumber) || CodeFlow.isIntegerForNume } if (leftOperand instanceof String && rightOperand instanceof Integer) { - int repeats = (Integer) rightOperand; - StringBuilder result = new StringBuilder(); - for (int i = 0; i < repeats; i++) { - result.append(leftOperand); - } - return new TypedValue(result.toString()); + String text = (String) leftOperand; + int count = (Integer) rightOperand; + 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()) { diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java index 553d91716e8b..e57edbc56ac5 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/OperatorTests.java @@ -21,10 +21,12 @@ import org.junit.Test; +import org.springframework.expression.Expression; import org.springframework.expression.spel.ast.Operator; import org.springframework.expression.spel.standard.SpelExpression; import static org.junit.Assert.*; +import static org.springframework.expression.spel.SpelMessage.MAX_REPEATED_TEXT_SIZE_EXCEEDED; /** * Tests the evaluation of expressions using relational operators. @@ -324,12 +326,6 @@ public void testRealLiteral() { evaluate("3.5", 3.5d, Double.class); } - @Test - public void testMultiplyStringInt() { - evaluate("'a' * 5", "aaaaa", String.class); - } - - @Test public void testMultiplyDoubleDoubleGivesDouble() { evaluate("3.0d * 5.0d", 15.0d, Double.class); } @@ -576,6 +572,19 @@ public void testStrings() { 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 public void testLongs() { evaluate("3L == 4L", false, Boolean.class);