From e8e293a706002a42859a86044fc103a3f5e255be Mon Sep 17 00:00:00 2001 From: junhyeong9812 Date: Fri, 14 Aug 2026 23:18:56 +0900 Subject: [PATCH] Reject write methods not starting with "set" in Property Prior to this commit, Property.resolveName() located the "set" prefix of a write method with String.indexOf(), which matches the token anywhere in the method name. A write method that merely contains "set" (for example "offsetX" or "upset") was silently accepted and resolved to a meaningless property name derived from whatever follows the token, while only names with no "set" token at all were rejected. To address that, this commit matches the "set" prefix only at the start of the method name via startsWith(), so that an IllegalArgumentException is consistently thrown for any write method candidate that is not a setter. See gh-36911 Closes gh-37139 Signed-off-by: junhyeong9812 --- .../core/convert/Property.java | 7 ++-- .../core/convert/PropertyTests.java | 39 ++++++++++++++++++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/convert/Property.java b/spring-core/src/main/java/org/springframework/core/convert/Property.java index 2967fe46ef7..1be7788b9ee 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/Property.java +++ b/spring-core/src/main/java/org/springframework/core/convert/Property.java @@ -153,12 +153,11 @@ public final class Property { return StringUtils.uncapitalize(methodName.substring(index)); } else if (this.writeMethod != null) { - int index = this.writeMethod.getName().indexOf("set"); - if (index == -1) { + String methodName = this.writeMethod.getName(); + if (!methodName.startsWith("set")) { throw new IllegalArgumentException("Not a setter method"); } - index += 3; - return StringUtils.uncapitalize(this.writeMethod.getName().substring(index)); + return StringUtils.uncapitalize(methodName.substring(3)); } else { throw new IllegalStateException("Property is neither readable nor writable"); diff --git a/spring-core/src/test/java/org/springframework/core/convert/PropertyTests.java b/spring-core/src/test/java/org/springframework/core/convert/PropertyTests.java index bc89545630c..0039faec473 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/PropertyTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/PropertyTests.java @@ -21,6 +21,7 @@ import java.lang.reflect.Method; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link Property} name resolution. @@ -47,8 +48,28 @@ class PropertyTests { @Test void resolveNameForSetter() throws Exception { - Method setter = TestBean.class.getMethod("setName", String.class); - assertThat(new Property(TestBean.class, null, setter).getName()).isEqualTo("name"); + assertThat(writeProperty("setName").getName()).isEqualTo("name"); + } + + @Test // no "set" token at all: rejected before and after this change + void rejectNonSetterWriteMethod() { + assertThatIllegalArgumentException() + .isThrownBy(() -> writeProperty("updateName")) + .withMessage("Not a setter method"); + } + + @Test // "set" embedded mid-name: formerly accepted and resolved to "x" + void rejectWriteMethodEmbeddingSetInName() { + assertThatIllegalArgumentException() + .isThrownBy(() -> writeProperty("offsetX")) + .withMessage("Not a setter method"); + } + + @Test // "set" at the end of the name: formerly accepted and resolved to "" + void rejectWriteMethodEndingWithSetToken() { + assertThatIllegalArgumentException() + .isThrownBy(() -> writeProperty("upset")) + .withMessage("Not a setter method"); } @Test // record component accessor whose name embeds the "get" prefix @@ -127,6 +148,11 @@ class PropertyTests { return new Property(objectType, readMethod, null); } + private static Property writeProperty(String writeMethodName) throws Exception { + Method writeMethod = TestBean.class.getMethod(writeMethodName, String.class); + return new Property(TestBean.class, null, writeMethod); + } + @SuppressWarnings("unused") static class TestBean { @@ -145,6 +171,15 @@ class PropertyTests { public void setName(String name) { } + + public void updateName(String name) { + } + + public void offsetX(String value) { + } + + public void upset(String value) { + } } record SampleRecord(String name, String budget, String issue) {