From ee1874ac52a0da4fda8ca9c315d9119f2b7f986e Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Sun, 12 Jul 2026 14:26:00 +0200 Subject: [PATCH] Check list index after auto-grow in AbstractNestablePropertyAccessor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prior to this commit, the List branch in AbstractNestablePropertyAccessor's getPropertyValue() method called list.get(index) unconditionally after invoking growCollectionIfNecessary(), which implicitly relied on the list throwing an IndexOutOfBoundsException for out-of-range access. Such an exception is caught downstream and wrapped as an InvalidPropertyException; however, any List implementation whose get() method allocates elements on demand rather than throwing an IndexOutOfBoundsException could bypass that check. This behavior was also inconsistent with the Collection/Iterable branch in the same method, which already performs an explicit `index >= collection.size()` bounds check before attempting element access. To address that, this commit aligns the List branch with the Collection/Iterable branch by adding an explicit `index < 0 || index >= list.size()` check immediately after the auto-grow attempt. If the index remains out of bounds after growCollectionIfNecessary() runs – for example, because growth was capped by autoGrowCollectionLimit or auto-growing was disabled – an InvalidPropertyException is now thrown rather than delegating to list.get() which may or may not throw an exception. Closes gh-37036 --- .../AbstractNestablePropertyAccessor.java | 5 ++ .../beans/BeanWrapperAutoGrowingTests.java | 50 ++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java index 8253a9fe9cd..4c763b480f5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java @@ -632,6 +632,11 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA else if (value instanceof List list) { int index = Integer.parseInt(key); growCollectionIfNecessary(list, index, indexedPropertyName.toString(), ph, i + 1); + if (index < 0 || index >= list.size()) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Cannot get element with index " + index + " from List of size " + + list.size() + ", accessed using property path '" + propertyName + "'"); + } value = list.get(index); } else if (value instanceof Map map) { diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java index 2b3480404fa..33194ed8400 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java @@ -16,6 +16,7 @@ package org.springframework.beans; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -183,7 +184,34 @@ class BeanWrapperAutoGrowingTests { wrapper.setAutoGrowCollectionLimit(2); assertThatExceptionOfType(InvalidPropertyException.class) .isThrownBy(() -> wrapper.getPropertyValue("list[4]")) - .withRootCauseInstanceOf(IndexOutOfBoundsException.class); + .withMessageContainingAll( + "Invalid property 'list[4]'", + "Cannot get element with index 4 from List of size 0"); + } + + @Test + void getPropertyValueSelfPopulatingListWorksWithinLimit() { + bean.setList(new SelfPopulatingList()); + assertThat(wrapper.getPropertyValue("list[2]")).isInstanceOf(Bean.class); + assertThat(bean.getList()) + .hasSize(3) + .allSatisfy(entry -> assertThat(entry).isInstanceOf(Bean.class)); + } + + @Test + void getPropertyValueSelfPopulatingListFailsAgainstLimit() { + bean.setList(new SelfPopulatingList()); + wrapper.setAutoGrowCollectionLimit(2); + assertThatExceptionOfType(InvalidPropertyException.class) + .isThrownBy(() -> wrapper.getPropertyValue("list[4]")); + } + + @Test + void setPropertyValueSelfPopulatingListFailsAgainstLimitForNestedPath() { + bean.setList(new SelfPopulatingList()); + wrapper.setAutoGrowCollectionLimit(2); + assertThatExceptionOfType(InvalidPropertyException.class) + .isThrownBy(() -> wrapper.setPropertyValue("list[4].prop", "test")); } @Test @@ -382,4 +410,24 @@ class BeanWrapperAutoGrowingTests { } } + + /** + * A {@link List} implementation that creates elements on demand in {@link #get(int)} + * instead of throwing {@link IndexOutOfBoundsException} for out-of-range indexes. + * + *
Used to verify that {@link BeanWrapperImpl} does not delegate to
+ * {@link List#get(int)} for indexes beyond the configured auto-grow limit.
+ */
+ @SuppressWarnings("serial")
+ private static class SelfPopulatingList extends ArrayList