Skip to content

Commit 9dabfe9

Browse files
committed
Account for all array objects when checking array size in SpEL
Prior to this commit, ConstructorReference.createArray() enforced the MAX_ARRAY_ELEMENTS threshold for multi-dimensional arrays by checking only the product of all dimension sizes, which is equivalent to the total number of leaf-level elements. However, Array.newInstance() allocates a distinct array object at every nesting level, not just at the leaf level. For dimensions [d0, d1, ..., dk-1], the total number of array objects created is 1 + d0 + d0*d1 + ... + d0*d1*...*d(k-2). As a result, an expression such as new int[262143][1][1]...[1], whose trailing dimensions are all 1, kept the leaf-element product just under the threshold while still causing tens of millions of array objects to be allocated. To address that, this commit introduces a second running total, totalArrayObjects, alongside the existing leaf-element product in the multi-dimensional array construction loop. Both totals are checked against MAX_ARRAY_ELEMENTS on every iteration, so array constructions that fan out into an excessive number of array objects are now rejected even when the leaf-element count remains within bounds. Note that SimpleEvaluationContext does not permit array construction in SpEL expressions at all, so this fix effectively only changes behavior for expressions evaluated via StandardEvaluationContext. Tests have been added to ArrayConstructorTests to verify that the new check rejects array constructions with an excessive number of array objects and that array constructions just under the threshold remain unaffected. Closes gh-36998
1 parent fb24082 commit 9dabfe9

2 files changed

Lines changed: 18 additions & 1 deletion

File tree

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ public class ConstructorReference extends SpelNodeImpl {
6767

6868
/**
6969
* Maximum number of elements permitted in an array declaration, applying
70-
* to one-dimensional as well as multi-dimensional arrays.
70+
* to one-dimensional as well as multi-dimensional arrays. For the latter,
71+
* this also bounds the total number of array objects allocated across all
72+
* nesting levels, not just the product of the dimension sizes.
7173
* @since 5.3.17
7274
*/
7375
private static final int MAX_ARRAY_ELEMENTS = 256 * 1024; // 256K
@@ -312,12 +314,18 @@ private TypedValue createArray(ExpressionState state) throws EvaluationException
312314
// Multidimensional - hold onto your hat!
313315
int[] dims = new int[this.dimensions.length];
314316
long numElements = 1;
317+
// Java allocates a distinct array object at every nesting level, so we
318+
// also have to cap the total number of array objects created, not just
319+
// the product of all dimension sizes (the number of leaf elements).
320+
long totalArrayObjects = 0;
315321
for (int d = 0; d < this.dimensions.length; d++) {
316322
TypedValue o = this.dimensions[d].getTypedValue(state);
317323
int arraySize = ExpressionUtils.toInt(typeConverter, o);
318324
dims[d] = arraySize;
325+
totalArrayObjects += numElements;
319326
numElements *= arraySize;
320327
checkNumElements(numElements);
328+
checkNumElements(totalArrayObjects);
321329
}
322330
newArray = Array.newInstance(componentType, dims);
323331
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ void errorCases() {
9595
int threshold = 256 * 1024; // ConstructorReference.MAX_ARRAY_ELEMENTS
9696
evaluateAndCheckError("new int[T(java.lang.Integer).MAX_VALUE]", SpelMessage.MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED, 0, threshold);
9797
evaluateAndCheckError("new int[1024 * 1024][1024 * 1024]", SpelMessage.MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED, 0, threshold);
98+
// The product of all dimension sizes stays just under the threshold, but Java
99+
// allocates an array object at every nesting level, so the total number of
100+
// array objects created (1 root + 262143 sub-arrays) meets the threshold.
101+
evaluateAndCheckError("new int[262143][1]", SpelMessage.MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED, 0, threshold);
102+
evaluateAndCheckError("new int[262143][1][1][1][1][1][1][1][1][1][1]",
103+
SpelMessage.MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED, 0, threshold);
98104
}
99105

100106
@Test
@@ -116,6 +122,9 @@ void multiDimensionalArrays() {
116122
evaluate("new String[3][2][1]",
117123
"[[Ljava.lang.String;[3]{[2]{[1]{null},[1]{null}},[2]{[1]{null},[1]{null}},[2]{[1]{null},[1]{null}}}",
118124
String[][][].class);
125+
// 1 root array + 262142 sub-arrays = 262143 array objects, just under the
126+
// MAX_ARRAY_ELEMENTS threshold, so this must not be rejected.
127+
evaluate("new int[262142][1].length", 262142, Integer.class);
119128
}
120129

121130
@Test

0 commit comments

Comments
 (0)