LANG-1433: MethodUtils will throw a NPE if invokeMethod() is called for a var-args method (#407)

* LANG-1433: MethodUtils will throw a NPE if invokeMethod() is called for a var-args method with null parameter value

* LANG-1433: Result of invokeMethod() is not deterministic for overloaded methods that can not be uniquly resolved from parameter types

* LANG-1433: Fixed checkstyle errors
This commit is contained in:
Christian Franzen 2020-02-17 16:29:34 +01:00 committed by GitHub
parent d3112e4306
commit 553a0474ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 53 additions and 15 deletions

View File

@ -162,7 +162,7 @@ private static float getTotalTransformationCost(final Class<?>[] srcArgs, final
// When isVarArgs is true, srcArgs and dstArgs may differ in length.
// There are two special cases to consider:
final boolean noVarArgsPassed = srcArgs.length < destArgs.length;
final boolean explicitArrayForVarags = srcArgs.length == destArgs.length && srcArgs[srcArgs.length-1].isArray();
final boolean explicitArrayForVarags = srcArgs.length == destArgs.length && srcArgs[srcArgs.length-1] != null && srcArgs[srcArgs.length-1].isArray();
final float varArgsCost = 0.001f;
final Class<?> destClass = destArgs[destArgs.length-1].getComponentType();
@ -227,6 +227,9 @@ private static float getObjectTransformationCost(Class<?> srcClass, final Class<
* @return The cost of promoting the primitive
*/
private static float getPrimitivePromotionCost(final Class<?> srcClass, final Class<?> destClass) {
if (srcClass == null) {
return 1.5f;
}
float cost = 0.0f;
Class<?> cls = srcClass;
if (!cls.isPrimitive()) {

View File

@ -25,6 +25,8 @@
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
@ -59,6 +61,13 @@
*/
public class MethodUtils {
private static final Comparator<Method> METHOD_BY_SIGNATURE = new Comparator<Method>() {
@Override
public int compare(final Method m1, final Method m2) {
return m1.toString ().compareTo (m2.toString ());
}
};
/**
* <p>{@link MethodUtils} instances should NOT be constructed in standard programming.
* Instead, the class should be used as
@ -462,8 +471,8 @@ private static Object[] toVarArgs(final Method method, Object[] args) {
* @since 3.5
*/
static Object[] getVarArgs(final Object[] args, final Class<?>[] methodParameterTypes) {
if (args.length == methodParameterTypes.length
&& args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1])) {
if (args.length == methodParameterTypes.length && (args[args.length - 1] == null ||
args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1]))) {
// The args array is already in the canonical form for the method.
return args;
}
@ -679,20 +688,28 @@ public static Method getMatchingAccessibleMethod(final Class<?> cls,
} catch (final NoSuchMethodException e) { // NOPMD - Swallow the exception
}
// search through all methods
Method bestMatch = null;
final Method[] methods = cls.getMethods();
final List<Method> matchingMethods = new ArrayList<>();
for (final Method method : methods) {
// compare name and parameters
if (method.getName().equals(methodName) &&
MemberUtils.isMatchingMethod(method, parameterTypes)) {
// get accessible version of method
final Method accessibleMethod = getAccessibleMethod(method);
if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareMethodFit(
accessibleMethod,
bestMatch,
parameterTypes) < 0)) {
bestMatch = accessibleMethod;
}
matchingMethods.add (method);
}
}
// Sort methods by signature to force deterministic result
Collections.sort (matchingMethods, METHOD_BY_SIGNATURE);
Method bestMatch = null;
for (final Method method : matchingMethods) {
// get accessible version of method
final Method accessibleMethod = getAccessibleMethod(method);
if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareMethodFit(
accessibleMethod,
bestMatch,
parameterTypes) < 0)) {
bestMatch = accessibleMethod;
}
}
if (bestMatch != null) {
@ -703,10 +720,12 @@ public static Method getMatchingAccessibleMethod(final Class<?> cls,
final Class<?>[] methodParameterTypes = bestMatch.getParameterTypes();
final Class<?> methodParameterComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
final String methodParameterComponentTypeName = ClassUtils.primitiveToWrapper(methodParameterComponentType).getName();
final String parameterTypeName = parameterTypes[parameterTypes.length - 1].getName();
final String parameterTypeSuperClassName = parameterTypes[parameterTypes.length - 1].getSuperclass().getName();
if (!methodParameterComponentTypeName.equals(parameterTypeName)
final Class<?> lastParameterType = parameterTypes[parameterTypes.length - 1];
final String parameterTypeName = (lastParameterType==null) ? null : lastParameterType.getName();
final String parameterTypeSuperClassName = (lastParameterType==null) ? null : lastParameterType.getSuperclass().getName();
if (parameterTypeName!= null && parameterTypeSuperClassName != null && !methodParameterComponentTypeName.equals(parameterTypeName)
&& !methodParameterComponentTypeName.equals(parameterTypeSuperClassName)) {
return null;
}

View File

@ -446,6 +446,22 @@ public void testInvokeMethod() throws Exception {
MethodUtils.invokeMethod(testBean, "varOverloadEcho", 17, 23, 42));
}
@Test
public void testInvokeMethod_VarArgsWithNullValues() throws Exception {
assertEquals("String...", MethodUtils.invokeMethod(testBean, "varOverload",
"a", null, "c"));
assertEquals("String...", MethodUtils.invokeMethod(testBean, "varOverload",
"a", "b", null));
}
@Test
public void testInvokeMethod_VarArgsNotUniqueResolvable() throws Exception {
assertEquals("Boolean...", MethodUtils.invokeMethod(testBean, "varOverload",
new Object[] {null}));
assertEquals("Object...", MethodUtils.invokeMethod(testBean, "varOverload",
(Object[]) null));
}
@Test
public void testInvokeExactMethod() throws Exception {
assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo",