Skip to content

Commit 46e3a91

Browse files
committed
Cache-safety check for sibling loaders resolving the same classes
Issue: SPR-16714
1 parent 1fa5f03 commit 46e3a91

File tree

2 files changed

+68
-20
lines changed

2 files changed

+68
-20
lines changed

spring-core/src/main/java/org/springframework/util/ClassUtils.java

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -406,24 +406,40 @@ public static boolean isCacheSafe(Class<?> clazz, @Nullable ClassLoader classLoa
406406
Assert.notNull(clazz, "Class must not be null");
407407
try {
408408
ClassLoader target = clazz.getClassLoader();
409-
if (target == null) {
409+
// Common cases
410+
if (target == classLoader || target == null) {
410411
return true;
411412
}
412-
ClassLoader cur = classLoader;
413-
if (cur == target) {
414-
return true;
413+
if (classLoader == null) {
414+
return false;
415415
}
416-
while (cur != null) {
417-
cur = cur.getParent();
418-
if (cur == target) {
416+
// Check for match in ancestors -> positive
417+
ClassLoader current = classLoader;
418+
while (current != null) {
419+
current = current.getParent();
420+
if (current == target) {
419421
return true;
420422
}
421423
}
422-
return false;
424+
// Check for match in children -> negative
425+
while (target != null) {
426+
target = target.getParent();
427+
if (target == classLoader) {
428+
return false;
429+
}
430+
}
423431
}
424432
catch (SecurityException ex) {
425-
// Probably from the system ClassLoader - let's consider it safe.
426-
return true;
433+
// Fall through to Class reference comparison below
434+
}
435+
436+
try {
437+
// Fallback for ClassLoaders without parent/child relationship:
438+
// safe if same Class can be loaded from given ClassLoader
439+
return (clazz == forName(clazz.getName(), classLoader));
440+
}
441+
catch (ClassNotFoundException ex) {
442+
return false;
427443
}
428444
}
429445

spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.util;
1818

19+
import java.io.Externalizable;
1920
import java.io.Serializable;
2021
import java.lang.reflect.InvocationTargetException;
2122
import java.lang.reflect.Method;
@@ -44,20 +45,21 @@
4445
* @author Rob Harrop
4546
* @author Rick Evans
4647
*/
47-
@SuppressWarnings({ "rawtypes", "unchecked" })
4848
public class ClassUtilsTests {
4949

5050
private ClassLoader classLoader = getClass().getClassLoader();
5151

52+
5253
@Before
53-
public void setUp() {
54+
public void clearStatics() {
5455
InnerClass.noArgCalled = false;
5556
InnerClass.argCalled = false;
5657
InnerClass.overloadedCalled = false;
5758
}
5859

60+
5961
@Test
60-
public void testIsPresent() throws Exception {
62+
public void testIsPresent() {
6163
assertTrue(ClassUtils.isPresent("java.lang.String", classLoader));
6264
assertFalse(ClassUtils.isPresent("java.lang.MySpecialString", classLoader));
6365
}
@@ -114,6 +116,36 @@ public void testForNameWithPrimitiveArraysInternalName() throws ClassNotFoundExc
114116
assertEquals(double[].class, ClassUtils.forName(double[].class.getName(), classLoader));
115117
}
116118

119+
@Test
120+
public void testIsCacheSafe() {
121+
ClassLoader childLoader1 = new ClassLoader(classLoader) {};
122+
ClassLoader childLoader2 = new ClassLoader(classLoader) {};
123+
ClassLoader childLoader3 = new ClassLoader(classLoader) {
124+
@Override
125+
public Class<?> loadClass(String name) throws ClassNotFoundException {
126+
return childLoader1.loadClass(name);
127+
}
128+
};
129+
Class<?> composite = ClassUtils.createCompositeInterface(
130+
new Class<?>[] {Serializable.class, Externalizable.class}, childLoader1);
131+
132+
assertTrue(ClassUtils.isCacheSafe(String.class, null));
133+
assertTrue(ClassUtils.isCacheSafe(String.class, classLoader));
134+
assertTrue(ClassUtils.isCacheSafe(String.class, childLoader1));
135+
assertTrue(ClassUtils.isCacheSafe(String.class, childLoader2));
136+
assertTrue(ClassUtils.isCacheSafe(String.class, childLoader3));
137+
assertFalse(ClassUtils.isCacheSafe(InnerClass.class, null));
138+
assertTrue(ClassUtils.isCacheSafe(InnerClass.class, classLoader));
139+
assertTrue(ClassUtils.isCacheSafe(InnerClass.class, childLoader1));
140+
assertTrue(ClassUtils.isCacheSafe(InnerClass.class, childLoader2));
141+
assertTrue(ClassUtils.isCacheSafe(InnerClass.class, childLoader3));
142+
assertFalse(ClassUtils.isCacheSafe(composite, null));
143+
assertFalse(ClassUtils.isCacheSafe(composite, classLoader));
144+
assertTrue(ClassUtils.isCacheSafe(composite, childLoader1));
145+
assertFalse(ClassUtils.isCacheSafe(composite, childLoader2));
146+
assertTrue(ClassUtils.isCacheSafe(composite, childLoader3));
147+
}
148+
117149
@Test
118150
public void testGetShortName() {
119151
String className = ClassUtils.getShortName(getClass());
@@ -199,15 +231,15 @@ public void testGetQualifiedNameForMultiDimensionalPrimitiveArrayClass() {
199231
}
200232

201233
@Test
202-
public void testHasMethod() throws Exception {
234+
public void testHasMethod() {
203235
assertTrue(ClassUtils.hasMethod(Collection.class, "size"));
204236
assertTrue(ClassUtils.hasMethod(Collection.class, "remove", Object.class));
205237
assertFalse(ClassUtils.hasMethod(Collection.class, "remove"));
206238
assertFalse(ClassUtils.hasMethod(Collection.class, "someOtherMethod"));
207239
}
208240

209241
@Test
210-
public void testGetMethodIfAvailable() throws Exception {
242+
public void testGetMethodIfAvailable() {
211243
Method method = ClassUtils.getMethodIfAvailable(Collection.class, "size");
212244
assertNotNull(method);
213245
assertEquals("size", method.getName());
@@ -278,7 +310,7 @@ public void testIsAssignable() {
278310
@Test
279311
public void testClassPackageAsResourcePath() {
280312
String result = ClassUtils.classPackageAsResourcePath(Proxy.class);
281-
assertTrue(result.equals("java/lang/reflect"));
313+
assertEquals("java/lang/reflect", result);
282314
}
283315

284316
@Test
@@ -294,7 +326,7 @@ public void testAddResourcePathToPackagePath() {
294326
@Test
295327
public void testGetAllInterfaces() {
296328
DerivedTestObject testBean = new DerivedTestObject();
297-
List ifcs = Arrays.asList(ClassUtils.getAllInterfaces(testBean));
329+
List<Class<?>> ifcs = Arrays.asList(ClassUtils.getAllInterfaces(testBean));
298330
assertEquals("Correct number of interfaces", 4, ifcs.size());
299331
assertTrue("Contains Serializable", ifcs.contains(Serializable.class));
300332
assertTrue("Contains ITestBean", ifcs.contains(ITestObject.class));
@@ -303,13 +335,13 @@ public void testGetAllInterfaces() {
303335

304336
@Test
305337
public void testClassNamesToString() {
306-
List ifcs = new LinkedList();
338+
List<Class<?>> ifcs = new LinkedList<>();
307339
ifcs.add(Serializable.class);
308340
ifcs.add(Runnable.class);
309341
assertEquals("[interface java.io.Serializable, interface java.lang.Runnable]", ifcs.toString());
310342
assertEquals("[java.io.Serializable, java.lang.Runnable]", ClassUtils.classNamesToString(ifcs));
311343

312-
List classes = new LinkedList();
344+
List<Class<?>> classes = new LinkedList<>();
313345
classes.add(LinkedList.class);
314346
classes.add(Integer.class);
315347
assertEquals("[class java.util.LinkedList, class java.lang.Integer]", classes.toString());
@@ -319,7 +351,7 @@ public void testClassNamesToString() {
319351
assertEquals("[java.util.List]", ClassUtils.classNamesToString(List.class));
320352

321353
assertEquals("[]", Collections.EMPTY_LIST.toString());
322-
assertEquals("[]", ClassUtils.classNamesToString(Collections.EMPTY_LIST));
354+
assertEquals("[]", ClassUtils.classNamesToString(Collections.emptyList()));
323355
}
324356

325357
@Test

0 commit comments

Comments
 (0)