Abort search for static methods in getPubliclyAccessibleMethodIfPossible()

Prior to this commit, getPubliclyAccessibleMethodIfPossible() in
ClassUtils incorrectly returned a hidden static method as an
"equivalent" method for a static method with the same signature;
however, a static method cannot be overridden and therefore has no
"equivalent" method in a super type.

To fix that bug, this commit immediately aborts the search for an
"equivalent" publicly accessible method when the original method is a
static method.

See gh-33216
See gh-35189
See gh-35556
Closes gh-35667
This commit is contained in:
Sam Brannen
2025-10-26 15:21:17 +01:00
parent 810e069bcc
commit b1f5b61bcd
3 changed files with 90 additions and 13 deletions
@@ -868,6 +868,49 @@ class ClassUtilsTests {
assertPubliclyAccessible(publiclyAccessibleMethod);
}
@Test // gh-35667
void staticMethodInPublicClass() throws Exception {
Method originalMethod = PublicSuperclass.class.getMethod("getCacheKey");
// Prerequisite: method must be public static for this use case.
assertPublic(originalMethod);
assertStatic(originalMethod);
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
assertPubliclyAccessible(publiclyAccessibleMethod);
}
@Test // gh-35667
void publicSubclassHidesStaticMethodInPublicSuperclass() throws Exception {
Method originalMethod = PublicSubclass.class.getMethod("getCacheKey");
// Prerequisite: type must be public for this use case.
assertPublic(originalMethod.getDeclaringClass());
// Prerequisite: method must be public static for this use case.
assertPublic(originalMethod);
assertStatic(originalMethod);
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
assertPubliclyAccessible(publiclyAccessibleMethod);
}
@Test // gh-35667
void privateSubclassHidesStaticMethodInPublicSuperclass() throws Exception {
Method originalMethod = PrivateSubclass.class.getMethod("getCacheKey");
// Prerequisite: type must not be public for this use case.
assertNotPublic(originalMethod.getDeclaringClass());
// Prerequisite: method must be public static for this use case.
assertPublic(originalMethod);
assertStatic(originalMethod);
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(originalMethod, null);
assertThat(publiclyAccessibleMethod).isSameAs(originalMethod);
assertNotPubliclyAccessible(publiclyAccessibleMethod);
}
}
@@ -914,6 +957,10 @@ class ClassUtilsTests {
return Modifier.isPublic(member.getModifiers());
}
private static void assertStatic(Member member) {
assertThat(Modifier.isStatic(member.getModifiers())).as("%s must be static", member).isTrue();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@@ -1048,8 +1095,27 @@ class ClassUtilsTests {
String greet(String name);
}
public static class PublicSubclass extends PublicSuperclass {
/**
* This method intentionally has the exact same signature as
* {@link PublicSuperclass#getCacheKey()}.
*/
public static String getCacheKey() {
return "child";
}
}
private static class PrivateSubclass extends PublicSuperclass implements PublicInterface, PrivateInterface {
/**
* This method intentionally has the exact same signature as
* {@link PublicSuperclass#getCacheKey()}.
*/
public static String getCacheKey() {
return "child";
}
@Override
public int getNumber() {
return 2;
@@ -21,6 +21,15 @@ package org.springframework.util;
*/
public class PublicSuperclass {
/**
* This method intentionally has the exact same signature as
* {@link org.springframework.util.ClassUtilsTests.PublicSubclass#getCacheKey()} and
* {@link org.springframework.util.ClassUtilsTests.PrivateSubclass#getCacheKey()}.
*/
public static String getCacheKey() {
return "parent";
}
public String getMessage() {
return "goodbye";
}