From 563b1c73a4e0158e6652acd8726d7f2aa4a43c46 Mon Sep 17 00:00:00 2001 From: John Rose Date: Wed, 10 Sep 2014 19:19:51 +0400 Subject: [PATCH] 8057656: Improve MethodType.isCastableTo() & MethodType.isConvertibleTo() checks Reviewed-by: vlivanov, psandoz --- .../java/lang/invoke/MethodHandles.java | 7 +- .../classes/java/lang/invoke/MethodType.java | 106 ++++++++++++++++-- 2 files changed, 99 insertions(+), 14 deletions(-) diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java index d12b7c210a4..47bff794491 100644 --- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java +++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java @@ -2024,8 +2024,11 @@ return invoker; */ public static MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) { - if (!target.type().isCastableTo(newType)) { - throw new WrongMethodTypeException("cannot explicitly cast "+target+" to "+newType); + MethodType oldType = target.type(); + // use the asTypeCache when possible: + if (oldType == newType) return target; + if (oldType.explicitCastEquivalentToAsType(newType)) { + return target.asType(newType); } return MethodHandleImpl.makePairwiseConvert(target, newType, false); } diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java b/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java index bf592fe1ca4..2762227f5c2 100644 --- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java +++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodType.java @@ -825,29 +825,111 @@ class MethodType implements java.io.Serializable { return true; } /*non-public*/ - boolean isCastableTo(MethodType newType) { - int argc = parameterCount(); - if (argc != newType.parameterCount()) - return false; - return true; - } - /*non-public*/ boolean isConvertibleTo(MethodType newType) { + MethodTypeForm oldForm = this.form(); + MethodTypeForm newForm = newType.form(); + if (oldForm == newForm) + // same parameter count, same primitive/object mix + return true; if (!canConvert(returnType(), newType.returnType())) return false; - int argc = parameterCount(); - if (argc != newType.parameterCount()) + Class[] srcTypes = newType.ptypes; + Class[] dstTypes = ptypes; + if (srcTypes == dstTypes) + return true; + int argc; + if ((argc = srcTypes.length) != dstTypes.length) return false; - for (int i = 0; i < argc; i++) { - if (!canConvert(newType.parameterType(i), parameterType(i))) + if (argc <= 1) { + if (argc == 1 && !canConvert(srcTypes[0], dstTypes[0])) return false; + return true; + } + if ((oldForm.primitiveParameterCount() == 0 && oldForm.erasedType == this) || + (newForm.primitiveParameterCount() == 0 && newForm.erasedType == newType)) { + // Somewhat complicated test to avoid a loop of 2 or more trips. + // If either type has only Object parameters, we know we can convert. + assert(canConvertParameters(srcTypes, dstTypes)); + return true; + } + return canConvertParameters(srcTypes, dstTypes); + } + + /** Returns true if MHs.explicitCastArguments produces the same result as MH.asType. + * If the type conversion is impossible for either, the result should be false. + */ + /*non-public*/ + boolean explicitCastEquivalentToAsType(MethodType newType) { + if (this == newType) return true; + if (!explicitCastEquivalentToAsType(rtype, newType.rtype)) { + return false; + } + Class[] srcTypes = newType.ptypes; + Class[] dstTypes = ptypes; + if (dstTypes == srcTypes) { + return true; + } + if (dstTypes.length != srcTypes.length) { + return false; + } + for (int i = 0; i < dstTypes.length; i++) { + if (!explicitCastEquivalentToAsType(srcTypes[i], dstTypes[i])) { + return false; + } } return true; } + + /** Reports true if the src can be converted to the dst, by both asType and MHs.eCE, + * and with the same effect. + * MHs.eCA has the following "upgrades" to MH.asType: + * 1. interfaces are unchecked (that is, treated as if aliased to Object) + * Therefore, {@code Object->CharSequence} is possible in both cases but has different semantics + * 2. the full matrix of primitive-to-primitive conversions is supported + * Narrowing like {@code long->byte} and basic-typing like {@code boolean->int} + * are not supported by asType, but anything supported by asType is equivalent + * with MHs.eCE. + * 3a. unboxing conversions can be followed by the full matrix of primitive conversions + * 3b. unboxing of null is permitted (creates a zero primitive value) + * Most unboxing conversions, like {@code Object->int}, has potentially + * different behaviors for asType vs. MHs.eCE, because the dynamic value + * might be a wrapper of a type that requires narrowing, like {@code (Object)1L->byte}. + * The equivalence is only certain if the static src type is a wrapper, + * and the conversion will be a widening one. + * Other than interfaces, reference-to-reference conversions are the same. + * Boxing primitives to references is the same for both operators. + */ + private static boolean explicitCastEquivalentToAsType(Class src, Class dst) { + if (src == dst || dst == Object.class || dst == void.class) return true; + if (src.isPrimitive()) { + // Could be a prim/prim conversion, where casting is a strict superset. + // Or a boxing conversion, which is always to an exact wrapper class. + return canConvert(src, dst); + } else if (dst.isPrimitive()) { + Wrapper dw = Wrapper.forPrimitiveType(dst); + // Watch out: If src is Number or Object, we could get dynamic narrowing conversion. + // The conversion is known to be widening only if the wrapper type is statically visible. + return (Wrapper.isWrapperType(src) && + dw.isConvertibleFrom(Wrapper.forWrapperType(src))); + } else { + // R->R always works, but we have to avoid a check-cast to an interface. + return !dst.isInterface() || dst.isAssignableFrom(src); + } + } + + private boolean canConvertParameters(Class[] srcTypes, Class[] dstTypes) { + for (int i = 0; i < srcTypes.length; i++) { + if (!canConvert(srcTypes[i], dstTypes[i])) { + return false; + } + } + return true; + } + /*non-public*/ static boolean canConvert(Class src, Class dst) { // short-circuit a few cases: - if (src == dst || dst == Object.class) return true; + if (src == dst || src == Object.class || dst == Object.class) return true; // the remainder of this logic is documented in MethodHandle.asType if (src.isPrimitive()) { // can force void to an explicit null, a la reflect.Method.invoke