diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java index e7b66721972..1947f157d32 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java @@ -67,7 +67,9 @@ public class ConstructorReference extends SpelNodeImpl { /** * Maximum number of elements permitted in an array declaration, applying - * to one-dimensional as well as multi-dimensional arrays. + * to one-dimensional as well as multi-dimensional arrays. For the latter, + * this also bounds the total number of array objects allocated across all + * nesting levels, not just the product of the dimension sizes. * @since 5.3.17 */ private static final int MAX_ARRAY_ELEMENTS = 256 * 1024; // 256K @@ -312,12 +314,18 @@ public class ConstructorReference extends SpelNodeImpl { // Multidimensional - hold onto your hat! int[] dims = new int[this.dimensions.length]; long numElements = 1; + // Java allocates a distinct array object at every nesting level, so we + // also have to cap the total number of array objects created, not just + // the product of all dimension sizes (the number of leaf elements). + long totalArrayObjects = 0; for (int d = 0; d < this.dimensions.length; d++) { TypedValue o = this.dimensions[d].getTypedValue(state); int arraySize = ExpressionUtils.toInt(typeConverter, o); dims[d] = arraySize; + totalArrayObjects += numElements; numElements *= arraySize; checkNumElements(numElements); + checkNumElements(totalArrayObjects); } newArray = Array.newInstance(componentType, dims); } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ArrayConstructorTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ArrayConstructorTests.java index f7b7f4753f9..b0b0ea4627f 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/ArrayConstructorTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/ArrayConstructorTests.java @@ -95,6 +95,12 @@ class ArrayConstructorTests extends AbstractExpressionTests { int threshold = 256 * 1024; // ConstructorReference.MAX_ARRAY_ELEMENTS evaluateAndCheckError("new int[T(java.lang.Integer).MAX_VALUE]", SpelMessage.MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED, 0, threshold); evaluateAndCheckError("new int[1024 * 1024][1024 * 1024]", SpelMessage.MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED, 0, threshold); + // The product of all dimension sizes stays just under the threshold, but Java + // allocates an array object at every nesting level, so the total number of + // array objects created (1 root + 262143 sub-arrays) meets the threshold. + evaluateAndCheckError("new int[262143][1]", SpelMessage.MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED, 0, threshold); + evaluateAndCheckError("new int[262143][1][1][1][1][1][1][1][1][1][1]", + SpelMessage.MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED, 0, threshold); } @Test @@ -116,6 +122,9 @@ class ArrayConstructorTests extends AbstractExpressionTests { evaluate("new String[3][2][1]", "[[Ljava.lang.String;[3]{[2]{[1]{null},[1]{null}},[2]{[1]{null},[1]{null}},[2]{[1]{null},[1]{null}}}", String[][][].class); + // 1 root array + 262142 sub-arrays = 262143 array objects, just under the + // MAX_ARRAY_ELEMENTS threshold, so this must not be rejected. + evaluate("new int[262142][1].length", 262142, Integer.class); } @Test