From fa6e16b4a5d7240a97bef43e251eeb3c30d3e954 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 31 Mar 2022 09:32:52 +0200 Subject: [PATCH] Improve diagnostics in SpEL for large array creation Attempting to create a large array in a SpEL expression can result in an OutOfMemoryError. Although the JVM recovers from that, the error message is not very helpful to the user. This commit improves the diagnostics in SpEL for large array creation by throwing a SpelEvaluationException with a meaningful error message in order to improve diagnostics for the user. Closes gh-28257 --- .../expression/spel/SpelMessage.java | 18 +++++++++++-- .../spel/ast/ConstructorReference.java | 26 ++++++++++++++++--- 2 files changed, 39 insertions(+), 5 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 834f63b55ab0..9ff75aece4a1 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-2014 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. @@ -34,6 +34,8 @@ * if it is known. * * @author Andy Clement + * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public enum SpelMessage { @@ -249,7 +251,19 @@ public enum SpelMessage { "Problem parsing left operand"), MISSING_SELECTION_EXPRESSION(Kind.ERROR, 1071, - "A required selection expression has not been specified"); + "A required selection expression has not been specified"), + + /** @since 4.1 */ + EXCEPTION_RUNNING_COMPILED_EXPRESSION(Kind.ERROR, 1072, + "An exception occurred whilst evaluating a compiled expression"), + + /** @since 4.3.17 */ + FLAWED_PATTERN(Kind.ERROR, 1073, + "Failed to efficiently evaluate pattern ''{0}'': consider redesigning it"), + + /** @since 5.2.20 */ + MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED(Kind.ERROR, 1075, + "Array declares too many elements, exceeding the threshold of ''{0}''"); private final Kind kind; 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 a16cc55788d2..3f97a1c15159 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 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. @@ -47,11 +47,19 @@ * * @author Andy Clement * @author Juergen Hoeller + * @author Sam Brannen * @since 3.0 */ public class ConstructorReference extends SpelNodeImpl { - private boolean isArrayConstructor = false; + /** + * Maximum number of elements permitted in an array declaration, applying + * to one-dimensional as well as multi-dimensional arrays. + * @since 5.2.20 + */ + private static final int MAX_ARRAY_ELEMENTS = 256 * 1024; // 256K + + private final boolean isArrayConstructor; private SpelNodeImpl[] dimensions; @@ -250,14 +258,19 @@ private TypedValue createArray(ExpressionState state) throws EvaluationException if (this.dimensions.length == 1) { TypedValue o = this.dimensions[0].getTypedValue(state); int arraySize = ExpressionUtils.toInt(typeConverter, o); + checkNumElements(arraySize); newArray = Array.newInstance(componentType, arraySize); } else { // Multi-dimensional - hold onto your hat! int[] dims = new int[this.dimensions.length]; + long numElements = 1; for (int d = 0; d < this.dimensions.length; d++) { TypedValue o = this.dimensions[d].getTypedValue(state); - dims[d] = ExpressionUtils.toInt(typeConverter, o); + int arraySize = ExpressionUtils.toInt(typeConverter, o); + dims[d] = arraySize; + numElements *= arraySize; + checkNumElements(numElements); } newArray = Array.newInstance(componentType, dims); } @@ -317,6 +330,13 @@ else if (arrayTypeCode == TypeCode.BYTE) { return new TypedValue(newArray); } + private void checkNumElements(long numElements) { + if (numElements >= MAX_ARRAY_ELEMENTS) { + throw new SpelEvaluationException(getStartPosition(), + SpelMessage.MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED, MAX_ARRAY_ELEMENTS); + } + } + private void populateReferenceTypeArray(ExpressionState state, Object newArray, TypeConverter typeConverter, InlineList initializer, Class componentType) { TypeDescriptor toTypeDescriptor = TypeDescriptor.valueOf(componentType);