Invoke target.toString() safely in ReflectionTestUtils

ReflectionTestUtils invokes toString() on target objects in order to
build exception and logging messages, and prior to this commit problems
could occur if the invocation of toString() threw an exception.

This commit addresses this issue by ensuring that all invocations of
toString() on target objects within ReflectionTestUtils are performed
safely within try-catch blocks.

Issue: SPR-14363
This commit is contained in:
Sam Brannen 2016-06-14 20:08:26 +02:00
parent 2fd4462268
commit 045ee52232
5 changed files with 128 additions and 33 deletions

View File

@ -173,14 +173,14 @@ public class ReflectionTestUtils {
Field field = ReflectionUtils.findField(targetClass, name, type);
if (field == null) {
throw new IllegalArgumentException(String.format(
"Could not find field '%s' of type [%s] on target object [%s] or target class [%s]", name, type,
ultimateTarget, targetClass));
"Could not find field '%s' of type [%s] on %s or target class [%s]", name, type,
safeToString(ultimateTarget), targetClass));
}
if (logger.isDebugEnabled()) {
logger.debug(String.format(
"Setting field '%s' of type [%s] on target object [%s] or target class [%s] to value [%s]", name, type,
ultimateTarget, targetClass, value));
"Setting field '%s' of type [%s] on %s or target class [%s] to value [%s]", name, type,
safeToString(ultimateTarget), targetClass, value));
}
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, ultimateTarget, value);
@ -253,14 +253,13 @@ public class ReflectionTestUtils {
Field field = ReflectionUtils.findField(targetClass, name);
if (field == null) {
throw new IllegalArgumentException(
String.format("Could not find field '%s' on target object [%s] or target class [%s]", name,
ultimateTarget, targetClass));
throw new IllegalArgumentException(String.format("Could not find field '%s' on %s or target class [%s]",
name, safeToString(ultimateTarget), targetClass));
}
if (logger.isDebugEnabled()) {
logger.debug(String.format("Getting field '%s' from target object [%s] or target class [%s]", name,
ultimateTarget, targetClass));
logger.debug(String.format("Getting field '%s' from %s or target class [%s]", name,
safeToString(ultimateTarget), targetClass));
}
ReflectionUtils.makeAccessible(field);
return ReflectionUtils.getField(field, ultimateTarget);
@ -327,13 +326,16 @@ public class ReflectionTestUtils {
method = ReflectionUtils.findMethod(target.getClass(), setterMethodName, paramTypes);
}
if (method == null) {
throw new IllegalArgumentException("Could not find setter method '" + setterMethodName +
"' on target [" + target + "] with parameter type [" + type + "]");
throw new IllegalArgumentException(String.format(
"Could not find setter method '%s' on %s with parameter type [%s]", setterMethodName,
safeToString(target), type));
}
if (logger.isDebugEnabled()) {
logger.debug("Invoking setter method '" + setterMethodName + "' on target [" + target + "]");
logger.debug(String.format("Invoking setter method '%s' on %s with value [%s]", setterMethodName,
safeToString(target), value));
}
ReflectionUtils.makeAccessible(method);
ReflectionUtils.invokeMethod(method, target, value);
}
@ -372,12 +374,12 @@ public class ReflectionTestUtils {
method = ReflectionUtils.findMethod(target.getClass(), getterMethodName);
}
if (method == null) {
throw new IllegalArgumentException("Could not find getter method '" + getterMethodName +
"' on target [" + target + "]");
throw new IllegalArgumentException(String.format(
"Could not find getter method '%s' on %s", getterMethodName, safeToString(target)));
}
if (logger.isDebugEnabled()) {
logger.debug("Invoking getter method '" + getterMethodName + "' on target [" + target + "]");
logger.debug(String.format("Invoking getter method '%s' on %s", getterMethodName, safeToString(target)));
}
ReflectionUtils.makeAccessible(method);
return ReflectionUtils.invokeMethod(method, target);
@ -412,8 +414,8 @@ public class ReflectionTestUtils {
methodInvoker.prepare();
if (logger.isDebugEnabled()) {
logger.debug("Invoking method '" + name + "' on target [" + target + "] with arguments [" +
ObjectUtils.nullSafeToString(args) + "]");
logger.debug(String.format("Invoking method '%s' on %s with arguments %s", name, safeToString(target),
ObjectUtils.nullSafeToString(args)));
}
return (T) methodInvoker.invoke();
@ -424,4 +426,14 @@ public class ReflectionTestUtils {
}
}
private static String safeToString(Object target) {
try {
return String.format("target object [%s]", target);
}
catch (Exception ex) {
return String.format("target of type [%s] whose toString() method threw [%s]",
(target != null ? target.getClass().getName() : "unknown"), ex);
}
}
}

View File

@ -48,6 +48,8 @@ public class ReflectionTestUtilsTests {
private final Component component = new Component();
private final LegacyEntity entity = new LegacyEntity();
@Rule
public final ExpectedException exception = ExpectedException.none();
@ -202,17 +204,6 @@ public class ReflectionTestUtilsTests {
setField(person, "likesPets", null, boolean.class);
}
/**
* Verifies behavior requested in <a href="https://jira.spring.io/browse/SPR-9571">SPR-9571</a>.
*/
@Test
public void setFieldOnLegacyEntityWithSideEffectsInToString() {
String testCollaborator = "test collaborator";
LegacyEntity entity = new LegacyEntity();
setField(entity, "collaborator", testCollaborator, Object.class);
assertTrue(entity.toString().contains(testCollaborator));
}
@Test
public void setStaticFieldViaClass() throws Exception {
setField(StaticFields.class, "publicField", "xxx");
@ -398,4 +389,37 @@ public class ReflectionTestUtilsTests {
invokeMethod(component, "configure", new Integer(42), "enigma", "baz", "quux");
}
@Test // SPR-14363
public void getFieldOnLegacyEntityWithSideEffectsInToString() {
Object collaborator = getField(entity, "collaborator");
assertNotNull(collaborator);
}
@Test // SPR-9571 and SPR-14363
public void setFieldOnLegacyEntityWithSideEffectsInToString() {
String testCollaborator = "test collaborator";
setField(entity, "collaborator", testCollaborator, Object.class);
assertTrue(entity.toString().contains(testCollaborator));
}
@Test // SPR-14363
public void invokeMethodOnLegacyEntityWithSideEffectsInToString() {
invokeMethod(entity, "configure", new Integer(42), "enigma");
assertEquals("number should have been configured", new Integer(42), entity.getNumber());
assertEquals("text should have been configured", "enigma", entity.getText());
}
@Test // SPR-14363
public void invokeGetterMethodOnLegacyEntityWithSideEffectsInToString() {
Object collaborator = invokeGetterMethod(entity, "collaborator");
assertNotNull(collaborator);
}
@Test // SPR-14363
public void invokeSetterMethodOnLegacyEntityWithSideEffectsInToString() {
String testCollaborator = "test collaborator";
invokeSetterMethod(entity, "collaborator", testCollaborator);
assertTrue(entity.toString().contains(testCollaborator));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2016 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.
@ -31,17 +31,41 @@ public class LegacyEntity {
@Override
public String toString() {
throw new RuntimeException(
throw new LegacyEntityException(
"Invoking toString() on the default collaborator causes an undesirable side effect");
};
}
};
private Integer number;
private String text;
public void configure(Integer number, String text) {
this.number = number;
this.text = text;
}
public Integer getNumber() {
return this.number;
}
public String getText() {
return this.text;
}
public Object getCollaborator() {
return this.collaborator;
}
public void setCollaborator(Object collaborator) {
this.collaborator = collaborator;
}
@Override
public String toString() {
return new ToStringCreator(this)//
.append("collaborator", this.collaborator)//
.toString();
.append("collaborator", this.collaborator)//
.toString();
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.util.subpackage;
/**
* Exception thrown by a {@link LegacyEntity}.
*
* @author Sam Brannen
* @since 4.3.1
*/
@SuppressWarnings("serial")
public class LegacyEntityException extends RuntimeException {
public LegacyEntityException(String message) {
super(message);
}
}

View File

@ -25,6 +25,9 @@ log4j.logger.org.springframework.test.context.web=WARN
#log4j.logger.org.springframework.test.context.support.AbstractGenericContextLoader=INFO
#log4j.logger.org.springframework.test.context.support.AnnotationConfigContextLoader=INFO
# The following must be kept at DEBUG in order to test SPR-14363.
log4j.logger.org.springframework.test.util=DEBUG
log4j.logger.org.springframework.test.web.servlet.result=DEBUG
#log4j.logger.org.springframework.test=TRACE