diff --git a/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc b/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc index ca5d2b830d0..e1e161bc574 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc @@ -554,6 +554,14 @@ Kotlin:: ---- ====== +When collection auto-growing is enabled, a collection cannot automatically grow beyond +256 elements by default; however, the `maximumAutoGrowSize` value is configurable. This +default is aligned with `DataBinder.DEFAULT_AUTO_GROW_COLLECTION_LIMIT`, for consistency +with the auto-grow limit used for data binding in Spring MVC and Spring WebFlux. If you +create a `SpelExpressionParser` programmatically, you can specify a custom +`maximumAutoGrowSize` when creating the `SpelParserConfiguration` that you provide to the +`SpelExpressionParser`. + By default, a SpEL expression cannot contain more than 10,000 characters; however, the `maxExpressionLength` is configurable. If you create a `SpelExpressionParser` programmatically, you can specify a custom `maxExpressionLength` when creating the 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 c62119f8fe1..175132a2b02 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 @@ -36,6 +36,15 @@ import org.springframework.util.StringUtils; */ public class SpelParserConfiguration { + /** + * Default maximum size to which a collection or array can automatically grow: {@value}. + *

Aligned with the default auto-grow limit used for Spring's data binding + * support (see {@code DataBinder.DEFAULT_AUTO_GROW_COLLECTION_LIMIT}), for + * consistency between SpEL and data binding. + * @since 7.1 + */ + public static final int DEFAULT_MAX_AUTO_GROW_SIZE = 256; + /** * Default maximum length permitted for a SpEL expression: {@value}. * @since 5.2.24 @@ -162,9 +171,10 @@ public class SpelParserConfiguration { * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME + * @see #DEFAULT_MAX_AUTO_GROW_SIZE */ public SpelParserConfiguration() { - this(null, null, false, false, Integer.MAX_VALUE); + this(null, null); } /** @@ -180,9 +190,10 @@ public class SpelParserConfiguration { * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME + * @see #DEFAULT_MAX_AUTO_GROW_SIZE */ public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader) { - this(compilerMode, compilerClassLoader, false, false, Integer.MAX_VALUE); + this(compilerMode, compilerClassLoader, false, false, DEFAULT_MAX_AUTO_GROW_SIZE); } /** @@ -196,9 +207,10 @@ public class SpelParserConfiguration { * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME + * @see #DEFAULT_MAX_AUTO_GROW_SIZE */ public SpelParserConfiguration(boolean autoGrowNullReferences, boolean autoGrowCollections) { - this(null, null, autoGrowNullReferences, autoGrowCollections, Integer.MAX_VALUE); + this(autoGrowNullReferences, autoGrowCollections, DEFAULT_MAX_AUTO_GROW_SIZE); } /** @@ -209,7 +221,9 @@ public class SpelParserConfiguration { * global defaults per use case. * @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 maximumAutoGrowSize the maximum size to which a collection can auto grow; + * must not be negative, and a value of {@code 0} effectively disables growing + * a collection beyond its current size * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME @@ -230,7 +244,9 @@ public class SpelParserConfiguration { * 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 maximumAutoGrowSize the maximum size to which a collection can auto grow; + * must not be negative, and a value of {@code 0} effectively disables growing + * a collection beyond its current size * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME @@ -254,7 +270,9 @@ public class SpelParserConfiguration { * 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 maximumAutoGrowSize the maximum size to which a collection can auto grow; + * must not be negative, and a value of {@code 0} effectively disables growing + * a collection beyond its current size * @param maximumExpressionLength the maximum length of a SpEL expression; * must be a positive number * @since 5.2.25 @@ -282,7 +300,9 @@ public class SpelParserConfiguration { * 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 maximumAutoGrowSize the maximum size to which a collection can auto grow; + * must not be negative, and a value of {@code 0} effectively disables growing + * a collection beyond its current size * @param maximumExpressionLength the maximum length of a SpEL expression; * must be a positive number * @param maximumOperations the maximum number of operations permitted during @@ -311,7 +331,9 @@ public class SpelParserConfiguration { * 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 maximumAutoGrowSize the maximum size to which a collection can auto grow; + * must not be negative, and a value of {@code 0} effectively disables growing + * a collection beyond its current size * @param maximumExpressionLength the maximum length of a SpEL expression; * must be a positive number * @param maximumOperations the maximum number of operations permitted during @@ -338,7 +360,9 @@ public class SpelParserConfiguration { * 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 maximumAutoGrowSize the maximum size to which a collection can auto grow; + * must not be negative, and a value of {@code 0} effectively disables growing + * a collection beyond its current size * @param maximumExpressionLength the maximum length of a SpEL expression; * must be a positive number * @param maximumOperations the maximum number of operations permitted during @@ -355,6 +379,7 @@ public class SpelParserConfiguration { int maximumOperations, int maximumBigPowerBits, int maximumNestingDepth) { Assert.notNull(compilerMode, "'compilerMode' must not be null"); + Assert.isTrue(maximumAutoGrowSize >= 0, "'maximumAutoGrowSize' must not be negative"); 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"); @@ -402,6 +427,7 @@ public class SpelParserConfiguration { /** * Return the maximum size to which a collection can auto grow. + * @see #DEFAULT_MAX_AUTO_GROW_SIZE */ public int getMaximumAutoGrowSize() { return this.maximumAutoGrowSize; diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java index 618e3450506..b44e24c923e 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java @@ -55,6 +55,7 @@ import org.springframework.expression.spel.testresources.Person; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; @@ -66,6 +67,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.springframework.expression.spel.SpelMessage.EXCEPTION_DURING_INDEX_READ; import static org.springframework.expression.spel.SpelMessage.EXCEPTION_DURING_INDEX_WRITE; import static org.springframework.expression.spel.SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE; +import static org.springframework.expression.spel.SpelMessage.UNABLE_TO_GROW_COLLECTION; import static org.springframework.expression.spel.SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE; @SuppressWarnings("rawtypes") @@ -326,6 +328,63 @@ class IndexingTests { .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE)); } + @Nested // gh-36995 + class MaxAutoGrowSizeTests { + + @Test + void defaultMaxAutoGrowSizeMatchesDataBinderDefault() { + SpelParserConfiguration configuration = new SpelParserConfiguration(true, true); + assertThat(configuration.getMaximumAutoGrowSize()) + .isEqualTo(SpelParserConfiguration.DEFAULT_MAX_AUTO_GROW_SIZE) + .isEqualTo(256); + } + + @Test + void zeroMaximumAutoGrowSizeIsAllowedAndDisablesGrowth() { + decimals = new ArrayList<>(); + SpelParserConfiguration configuration = new SpelParserConfiguration(true, true, 0); + SpelExpressionParser parser = new SpelExpressionParser(configuration); + + Expression indexExpression = parser.parseExpression("decimals[0]"); + assertThatExceptionOfType(SpelEvaluationException.class) + .isThrownBy(() -> indexExpression.getValue(IndexingTests.this)) + .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(UNABLE_TO_GROW_COLLECTION)); + } + + @Test + void negativeMaximumAutoGrowSizeIsRejected() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new SpelParserConfiguration(true, true, -1)) + .withMessage("'maximumAutoGrowSize' must not be negative"); + } + + @Test + void collectionGrowsUpToDefaultMaxAutoGrowSizeButNotBeyond() { + decimals = new ArrayList<>(); + SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + + // Growing up to the default maximum auto-grow size should succeed. + parser.parseExpression("decimals[255]").getValue(IndexingTests.this); + assertThat(decimals).hasSize(256); + + // Growing beyond the default maximum auto-grow size should fail. + Expression indexExpression = parser.parseExpression("decimals[256]"); + assertThatExceptionOfType(SpelEvaluationException.class) + .isThrownBy(() -> indexExpression.getValue(IndexingTests.this)) + .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(UNABLE_TO_GROW_COLLECTION)); + } + + @Test + void collectionCanGrowBeyondDefaultMaxAutoGrowSizeWhenConfiguredExplicitly() { + decimals = new ArrayList<>(); + SpelParserConfiguration configuration = new SpelParserConfiguration(true, true, 1000); + SpelExpressionParser parser = new SpelExpressionParser(configuration); + + parser.parseExpression("decimals[500]").getValue(IndexingTests.this); + assertThat(decimals).hasSize(501); + } + } + @Test void indexIntoGenericPropertyContainingArray() { String[] property = { "bar" };