From 70e0218c22b70c26802f923f0d946adfe3893dc5 Mon Sep 17 00:00:00 2001 From: jrose Date: Sat, 30 Oct 2010 21:02:30 -0700 Subject: [PATCH] 6939224: MethodHandle.invokeGeneric needs to perform the correct set of conversions Summary: JDK changes which run atop the corresponding JVM hook Reviewed-by: never, twisti --- src/share/classes/java/dyn/MethodHandle.java | 7 +- src/share/classes/java/dyn/MethodHandles.java | 58 ++- .../classes/sun/dyn/AdapterMethodHandle.java | 33 ++ .../classes/sun/dyn/BoundMethodHandle.java | 2 +- src/share/classes/sun/dyn/InvokeGeneric.java | 160 ++++++ src/share/classes/sun/dyn/Invokers.java | 17 + .../classes/sun/dyn/MethodHandleImpl.java | 9 +- .../classes/sun/dyn/MethodHandleNatives.java | 14 + src/share/classes/sun/dyn/MethodTypeImpl.java | 2 + .../sun/dyn/util/ValueConversions.java | 71 ++- test/java/dyn/InvokeGenericTest.java | 484 ++++++++++++++++++ test/java/dyn/MethodHandlesTest.java | 2 +- 12 files changed, 847 insertions(+), 12 deletions(-) create mode 100644 src/share/classes/sun/dyn/InvokeGeneric.java create mode 100644 test/java/dyn/InvokeGenericTest.java diff --git a/src/share/classes/java/dyn/MethodHandle.java b/src/share/classes/java/dyn/MethodHandle.java index 4904e9753..251d338ce 100644 --- a/src/share/classes/java/dyn/MethodHandle.java +++ b/src/share/classes/java/dyn/MethodHandle.java @@ -329,6 +329,7 @@ public abstract class MethodHandle public final Object invokeVarargs(Object... arguments) throws Throwable { int argc = arguments == null ? 0 : arguments.length; MethodType type = type(); + if (type.parameterCount() != argc) throw badParameterCount(type, argc); if (argc <= 10) { MethodHandle invoker = MethodHandles.invokers(type).genericInvoker(); switch (argc) { @@ -377,6 +378,10 @@ public abstract class MethodHandle return invokeVarargs(arguments.toArray()); } + private static WrongMethodTypeException badParameterCount(MethodType type, int argc) { + return new WrongMethodTypeException(type+" does not take "+argc+" parameters"); + } + /* --- this is intentionally NOT a javadoc yet --- * PROVISIONAL API, WORK IN PROGRESS: * Produce an adapter method handle which adapts the type of the @@ -446,7 +451,7 @@ public abstract class MethodHandle * @throws IllegalArgumentException if the conversion cannot be made * @see MethodHandles#convertArguments */ - public final MethodHandle asType(MethodType newType) { + public MethodHandle asType(MethodType newType) { return MethodHandles.convertArguments(this, newType); } diff --git a/src/share/classes/java/dyn/MethodHandles.java b/src/share/classes/java/dyn/MethodHandles.java index b6e833383..3e1fef7ff 100644 --- a/src/share/classes/java/dyn/MethodHandles.java +++ b/src/share/classes/java/dyn/MethodHandles.java @@ -1068,10 +1068,14 @@ public class MethodHandles { MethodType oldType = target.type(); if (oldType.equals(newType)) return target; - MethodHandle res = MethodHandleImpl.convertArguments(IMPL_TOKEN, target, - newType, oldType, null); + MethodHandle res = null; + try { + res = MethodHandleImpl.convertArguments(IMPL_TOKEN, target, + newType, oldType, null); + } catch (IllegalArgumentException ex) { + } if (res == null) - throw newIllegalArgumentException("cannot convert to "+newType+": "+target); + throw new WrongMethodTypeException("cannot convert to "+newType+": "+target); return res; } @@ -1392,6 +1396,20 @@ public class MethodHandles { return adapter; } + /** Apply the given filter function to the return value of the given target. + */ + /*public*/ static + MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) { + MethodType targetType = target.type(); + MethodType filterType = filter.type(); + if (filterType.parameterCount() != 1 + || filterType.parameterType(0) != targetType.returnType()) + throw newIllegalArgumentException("target and filter types do not match"); + // FIXME: Too many nodes here. + MethodHandle returner = dropArguments(filter, 0, targetType.parameterList()); + return foldArguments(returner, exactInvoker(target.type()).bindTo(target)); + } + /** * PROVISIONAL API, WORK IN PROGRESS: * Adapt a target method handle {@code target} by pre-processing @@ -1434,7 +1452,7 @@ public class MethodHandles { * @return method handle which incorporates the specified argument folding logic * @throws IllegalArgumentException if the first argument type of * {@code target} is not the same as {@code combiner}'s return type, - * or if the next {@code foldArgs} argument types of {@code target} + * or if the following argument types of {@code target} * are not identical with the argument types of {@code combiner} */ public static @@ -1443,12 +1461,39 @@ public class MethodHandles { MethodType combinerType = combiner.type(); int foldArgs = combinerType.parameterCount(); boolean ok = (targetType.parameterCount() >= 1 + foldArgs); + if (ok && !combinerType.parameterList().equals(targetType.parameterList().subList(1, foldArgs+1))) + ok = false; + if (ok && !combinerType.returnType().equals(targetType.parameterType(0))) + ok = false; if (!ok) throw misMatchedTypes("target and combiner types", targetType, combinerType); MethodType newType = targetType.dropParameterTypes(0, 1); return MethodHandleImpl.foldArguments(IMPL_TOKEN, target, newType, combiner); } + // /** + // * PROVISIONAL API, WORK IN PROGRESS: + // * Adapt a target method handle {@code target} by pre-processing + // * some of its arguments to derive a new target method handle. + // * Call the new target on the original arguments. + // * @param combined method handle to call initially on the incoming arguments + // * @return method handle which incorporates the specified dispatching logic + // * @throws IllegalArgumentException if the first argument type of + // * {@code combiner}'s return type is not {@link MethodHandle}, + // * or if the next argument types of {@code target} + // * are not identical with the argument types of {@code combiner} + // */ + // public static + // MethodHandle dispatchArguments(MethodType targetType, MethodHandle dispatcher) { + // MethodType dispatcherType = dispatcher.type(); + // int foldArgs = dispatcherType.parameterCount(); + // boolean ok = (targetType.parameterCount() >= foldArgs); + // if (!ok) + // throw misMatchedTypes("target and dispatcher types", targetType, dispatcherType); + // MethodHandle target = exactInvoker(targetType); + // return MethodHandleImpl.foldArguments(IMPL_TOKEN, target, targetType, dispatcher); + // } + /** * PROVISIONAL API, WORK IN PROGRESS: * Make a method handle which adapts a target method handle, @@ -1691,4 +1736,9 @@ public class MethodHandles { } return null; } + + /*non-public*/ + static MethodHandle withTypeHandler(MethodHandle target, MethodHandle typeHandler) { + return MethodHandleImpl.withTypeHandler(IMPL_TOKEN, target, typeHandler); + } } diff --git a/src/share/classes/sun/dyn/AdapterMethodHandle.java b/src/share/classes/sun/dyn/AdapterMethodHandle.java index fb9b7fa26..65bbd372b 100644 --- a/src/share/classes/sun/dyn/AdapterMethodHandle.java +++ b/src/share/classes/sun/dyn/AdapterMethodHandle.java @@ -478,6 +478,39 @@ public class AdapterMethodHandle extends BoundMethodHandle { return new AdapterMethodHandle(target, newType, makeConv(raw ? OP_RETYPE_RAW : OP_RETYPE_ONLY)); } + static MethodHandle makeTypeHandler(Access token, + MethodHandle target, MethodHandle typeHandler) { + Access.check(token); + return new WithTypeHandler(target, typeHandler); + } + + static class WithTypeHandler extends AdapterMethodHandle { + final MethodHandle target, typeHandler; + WithTypeHandler(MethodHandle target, MethodHandle typeHandler) { + super(target, target.type(), OP_RETYPE_ONLY); + this.target = target; + this.typeHandler = typeHandler.asType(TYPE_HANDLER_TYPE); + } + + public MethodHandle asType(MethodType newType) { + if (this.type() == newType) + return this; + try { + MethodHandle retyped = (MethodHandle) typeHandler.invokeExact(target, newType); + // Contract: Must return the desired type, or throw WMT + if (retyped.type() != newType) + throw new WrongMethodTypeException(retyped.toString()); + return retyped; + } catch (Throwable ex) { + if (ex instanceof Error) throw (Error)ex; + if (ex instanceof RuntimeException) throw (RuntimeException)ex; + throw new RuntimeException(ex); + } + } + private static final MethodType TYPE_HANDLER_TYPE + = MethodType.methodType(MethodHandle.class, MethodHandle.class, MethodType.class); + } + /** Can a checkcast adapter validly convert the target to newType? * The JVM supports all kind of reference casts, even silly ones. */ diff --git a/src/share/classes/sun/dyn/BoundMethodHandle.java b/src/share/classes/sun/dyn/BoundMethodHandle.java index f2ae32efb..4f25262f4 100644 --- a/src/share/classes/sun/dyn/BoundMethodHandle.java +++ b/src/share/classes/sun/dyn/BoundMethodHandle.java @@ -103,7 +103,7 @@ public class BoundMethodHandle extends MethodHandle { super(Access.TOKEN, type); this.argument = argument; this.vmargslot = vmargslot; - assert(this.getClass() == AdapterMethodHandle.class); + assert(this instanceof AdapterMethodHandle); } /** Initialize the current object as a Java method handle, binding it diff --git a/src/share/classes/sun/dyn/InvokeGeneric.java b/src/share/classes/sun/dyn/InvokeGeneric.java new file mode 100644 index 000000000..8137bb4a6 --- /dev/null +++ b/src/share/classes/sun/dyn/InvokeGeneric.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.dyn; + +import java.dyn.*; +import java.lang.reflect.*; +import sun.dyn.util.*; + +/** + * Adapters which manage MethodHanndle.invokeGeneric calls. + * The JVM calls one of these when the exact type match fails. + * @author jrose + */ +class InvokeGeneric { + // erased type for the call, which originates from an invokeGeneric site + private final MethodType erasedCallerType; + // an invoker of type (MT, MH; A...) -> R + private final MethodHandle initialInvoker; + + /** Compute and cache information for this adapter, so that it can + * call out to targets of the erasure-family of the given erased type. + */ + private InvokeGeneric(MethodType erasedCallerType) throws NoAccessException { + this.erasedCallerType = erasedCallerType; + this.initialInvoker = makeInitialInvoker(); + assert initialInvoker.type().equals(erasedCallerType + .insertParameterTypes(0, MethodType.class, MethodHandle.class)) + : initialInvoker.type(); + } + + private static MethodHandles.Lookup lookup() { + return MethodHandleImpl.IMPL_LOOKUP; + } + + /** Return the adapter information for this type's erasure. */ + static MethodHandle genericInvokerOf(MethodType type) { + MethodTypeImpl form = MethodTypeImpl.of(type); + MethodHandle genericInvoker = form.genericInvoker; + if (genericInvoker == null) { + try { + InvokeGeneric gen = new InvokeGeneric(form.erasedType()); + form.genericInvoker = genericInvoker = gen.initialInvoker; + } catch (NoAccessException ex) { + throw new RuntimeException(ex); + } + } + return genericInvoker; + } + + private MethodHandle makeInitialInvoker() throws NoAccessException { + // postDispatch = #(MH'; MT, MH; A...){MH'(MT, MH; A)} + MethodHandle postDispatch = makePostDispatchInvoker(); + MethodHandle invoker; + if (returnConversionPossible()) { + invoker = MethodHandles.foldArguments(postDispatch, + dispatcher("dispatchWithConversion")); + } else { + invoker = MethodHandles.foldArguments(postDispatch, dispatcher("dispatch")); + } + return invoker; + } + + private static final Class[] EXTRA_ARGS = { MethodType.class, MethodHandle.class }; + private MethodHandle makePostDispatchInvoker() { + // Take (MH'; MT, MH; A...) and run MH'(MT, MH; A...). + MethodType invokerType = erasedCallerType.insertParameterTypes(0, EXTRA_ARGS); + return MethodHandles.exactInvoker(invokerType); + } + private MethodHandle dropDispatchArguments(MethodHandle targetInvoker) { + assert(targetInvoker.type().parameterType(0) == MethodHandle.class); + return MethodHandles.dropArguments(targetInvoker, 1, EXTRA_ARGS); + } + + private MethodHandle dispatcher(String dispatchName) throws NoAccessException { + return lookup().bind(this, dispatchName, + MethodType.methodType(MethodHandle.class, + MethodType.class, MethodHandle.class)); + } + + static final boolean USE_AS_TYPE_PATH = true; + + /** Return a method handle to invoke on the callerType, target, and remaining arguments. + * The method handle must finish the call. + * This is the first look at the caller type and target. + */ + private MethodHandle dispatch(MethodType callerType, MethodHandle target) { + MethodType targetType = target.type(); + if (USE_AS_TYPE_PATH || target instanceof AdapterMethodHandle.WithTypeHandler) { + MethodHandle newTarget = target.asType(callerType); + targetType = callerType; + Invokers invokers = MethodTypeImpl.invokers(Access.TOKEN, targetType); + MethodHandle invoker = invokers.erasedInvokerWithDrops; + if (invoker == null) { + invokers.erasedInvokerWithDrops = invoker = + dropDispatchArguments(invokers.erasedInvoker()); + } + return invoker.bindTo(newTarget); + } + throw new RuntimeException("NYI"); + } + + private MethodHandle dispatchWithConversion(MethodType callerType, MethodHandle target) { + MethodHandle finisher = dispatch(callerType, target); + if (returnConversionNeeded(callerType, target)) + finisher = addReturnConversion(finisher, callerType.returnType()); //FIXME: slow + return finisher; + } + + private boolean returnConversionPossible() { + Class needType = erasedCallerType.returnType(); + return !needType.isPrimitive(); + } + private boolean returnConversionNeeded(MethodType callerType, MethodHandle target) { + Class needType = callerType.returnType(); + if (needType == erasedCallerType.returnType()) + return false; // no conversions possible, since must be primitive or Object + Class haveType = target.type().returnType(); + if (VerifyType.isNullConversion(haveType, needType)) + return false; + return true; + } + private MethodHandle addReturnConversion(MethodHandle target, Class type) { + if (true) throw new RuntimeException("NYI"); + // FIXME: This is slow because it creates a closure node on every call that requires a return cast. + MethodType targetType = target.type(); + MethodHandle caster = ValueConversions.identity(type); + caster = caster.asType(MethodType.methodType(type, targetType.returnType())); + // Drop irrelevant arguments, because we only care about the return value: + caster = MethodHandles.dropArguments(caster, 1, targetType.parameterList()); + MethodHandle result = MethodHandles.foldArguments(caster, target); + return result.asType(target.type()); + } + + public String toString() { + return "InvokeGeneric"+erasedCallerType; + } +} diff --git a/src/share/classes/sun/dyn/Invokers.java b/src/share/classes/sun/dyn/Invokers.java index b3d2823d0..2f2c92a77 100644 --- a/src/share/classes/sun/dyn/Invokers.java +++ b/src/share/classes/sun/dyn/Invokers.java @@ -38,6 +38,10 @@ public class Invokers { // exact invoker for the outgoing call private /*lazy*/ MethodHandle exactInvoker; + // erased (partially untyped but with primitives) invoker for the outgoing call + private /*lazy*/ MethodHandle erasedInvoker; + /*lazy*/ MethodHandle erasedInvokerWithDrops; // for InvokeGeneric + // generic (untyped) invoker for the outgoing call private /*lazy*/ MethodHandle genericInvoker; @@ -80,6 +84,19 @@ public class Invokers { return invoker; } + public MethodHandle erasedInvoker() { + MethodHandle invoker1 = exactInvoker(); + MethodHandle invoker = erasedInvoker; + if (invoker != null) return invoker; + MethodType erasedType = targetType.erase(); + if (erasedType == targetType.generic()) + invoker = genericInvoker(); + else + invoker = MethodHandles.convertArguments(invoker1, invokerType(erasedType)); + erasedInvoker = invoker; + return invoker; + } + public MethodHandle varargsInvoker(int objectArgCount) { MethodHandle vaInvoker = varargsInvokers[objectArgCount]; if (vaInvoker != null) return vaInvoker; diff --git a/src/share/classes/sun/dyn/MethodHandleImpl.java b/src/share/classes/sun/dyn/MethodHandleImpl.java index caa96b90d..49a625367 100644 --- a/src/share/classes/sun/dyn/MethodHandleImpl.java +++ b/src/share/classes/sun/dyn/MethodHandleImpl.java @@ -560,7 +560,9 @@ public abstract class MethodHandleImpl { MethodHandle bindReceiver(Access token, MethodHandle target, Object receiver) { Access.check(token); - if (target instanceof AdapterMethodHandle) { + if (target instanceof AdapterMethodHandle && + ((AdapterMethodHandle)target).conversionOp() == MethodHandleNatives.Constants.OP_RETYPE_ONLY + ) { Object info = MethodHandleNatives.getTargetInfo(target); if (info instanceof DirectMethodHandle) { DirectMethodHandle dmh = (DirectMethodHandle) info; @@ -1257,4 +1259,9 @@ public abstract class MethodHandleImpl { Access.check(token); return MethodHandleNatives.getBootstrap(callerClass); } + + public static MethodHandle withTypeHandler(Access token, MethodHandle target, MethodHandle typeHandler) { + Access.check(token); + return AdapterMethodHandle.makeTypeHandler(token, target, typeHandler); + } } diff --git a/src/share/classes/sun/dyn/MethodHandleNatives.java b/src/share/classes/sun/dyn/MethodHandleNatives.java index 47a9a2d3d..3d0629dd0 100644 --- a/src/share/classes/sun/dyn/MethodHandleNatives.java +++ b/src/share/classes/sun/dyn/MethodHandleNatives.java @@ -316,6 +316,20 @@ class MethodHandleNatives { return MethodTypeImpl.makeImpl(Access.TOKEN, rtype, ptypes, true); } + /** + * The JVM wants to use a MethodType with invokeGeneric. Give the runtime fair warning. + */ + static void notifyGenericMethodType(MethodType type) { + try { + // Trigger adapter creation. + InvokeGeneric.genericInvokerOf(type); + } catch (Exception ex) { + Error err = new InternalError("Exception while resolving invokeGeneric"); + err.initCause(ex); + throw err; + } + } + /** * The JVM is resolving a CONSTANT_MethodHandle CP entry. And it wants our help. * It will make an up-call to this method. (Do not change the name or signature.) diff --git a/src/share/classes/sun/dyn/MethodTypeImpl.java b/src/share/classes/sun/dyn/MethodTypeImpl.java index 0a318148a..059f26975 100644 --- a/src/share/classes/sun/dyn/MethodTypeImpl.java +++ b/src/share/classes/sun/dyn/MethodTypeImpl.java @@ -48,6 +48,7 @@ public class MethodTypeImpl { final long primCounts; // packed prim & double counts final int vmslots; // total number of parameter slots final MethodType erasedType; // the canonical erasure + /*lazy*/ MethodType primsAsBoxes; // replace prims by wrappers /*lazy*/ MethodType primArgsAsBoxes; // wrap args only; make raw return /*lazy*/ MethodType primsAsInts; // replace prims by int/long @@ -59,6 +60,7 @@ public class MethodTypeImpl { /*lazy*/ FromGeneric fromGeneric; // convert cs. w/o prims to with /*lazy*/ SpreadGeneric[] spreadGeneric; // expand one argument to many /*lazy*/ FilterGeneric filterGeneric; // convert argument(s) on the fly + /*lazy*/ MethodHandle genericInvoker; // hook for invokeGeneric public MethodType erasedType() { return erasedType; diff --git a/src/share/classes/sun/dyn/util/ValueConversions.java b/src/share/classes/sun/dyn/util/ValueConversions.java index f5ee5cb81..aeacb9e0f 100644 --- a/src/share/classes/sun/dyn/util/ValueConversions.java +++ b/src/share/classes/sun/dyn/util/ValueConversions.java @@ -377,7 +377,7 @@ public class ValueConversions { REBOX_CONVERSIONS = newWrapperCaches(2); /** - * Becase we normalize primitive types to reduce the number of signatures, + * Because we normalize primitive types to reduce the number of signatures, * primitives are sometimes manipulated under an "erased" type, * either int (for types other than long/double) or long (for all types). * When the erased primitive value is then boxed into an Integer or Long, @@ -475,10 +475,10 @@ public class ValueConversions { } private static final EnumMap[] - ZERO_CONSTANT_FUNCTIONS = newWrapperCaches(1); + CONSTANT_FUNCTIONS = newWrapperCaches(2); public static MethodHandle zeroConstantFunction(Wrapper wrap) { - EnumMap cache = ZERO_CONSTANT_FUNCTIONS[0]; + EnumMap cache = CONSTANT_FUNCTIONS[0]; MethodHandle mh = cache.get(wrap); if (mh != null) { return mh; @@ -543,6 +543,24 @@ public class ValueConversions { return x; } + /** + * Identity function on ints. + * @param x an arbitrary int value + * @return the same value x + */ + static int identity(int x) { + return x; + } + + /** + * Identity function on longs. + * @param x an arbitrary long value + * @return the same value x + */ + static long identity(long x) { + return x; + } + /** * Identity function, with reference cast. * @param t an arbitrary reference type @@ -553,7 +571,7 @@ public class ValueConversions { return t.cast(x); } - private static final MethodHandle IDENTITY, CAST_REFERENCE, ALWAYS_NULL, ALWAYS_ZERO, ZERO_OBJECT, IGNORE, EMPTY; + private static final MethodHandle IDENTITY, IDENTITY_I, IDENTITY_J, CAST_REFERENCE, ALWAYS_NULL, ALWAYS_ZERO, ZERO_OBJECT, IGNORE, EMPTY; static { try { MethodType idType = MethodType.genericMethodType(1); @@ -562,6 +580,8 @@ public class ValueConversions { MethodType ignoreType = idType.changeReturnType(void.class); MethodType zeroObjectType = MethodType.genericMethodType(0); IDENTITY = IMPL_LOOKUP.findStatic(ValueConversions.class, "identity", idType); + IDENTITY_I = IMPL_LOOKUP.findStatic(ValueConversions.class, "identity", MethodType.methodType(int.class, int.class)); + IDENTITY_J = IMPL_LOOKUP.findStatic(ValueConversions.class, "identity", MethodType.methodType(long.class, long.class)); //CAST_REFERENCE = IMPL_LOOKUP.findVirtual(Class.class, "cast", idType); CAST_REFERENCE = IMPL_LOOKUP.findStatic(ValueConversions.class, "castReference", castType); ALWAYS_NULL = IMPL_LOOKUP.findStatic(ValueConversions.class, "alwaysNull", idType); @@ -613,6 +633,49 @@ public class ValueConversions { return IDENTITY; } + public static MethodHandle identity(Class type) { + if (type == Object.class) + return IDENTITY; + else if (!type.isPrimitive()) + return retype(MethodType.methodType(type, type), IDENTITY); + else + return identity(Wrapper.forPrimitiveType(type)); + } + + static MethodHandle identity(Wrapper wrap) { + EnumMap cache = CONSTANT_FUNCTIONS[1]; + MethodHandle mh = cache.get(wrap); + if (mh != null) { + return mh; + } + // slow path + MethodType type = MethodType.methodType(wrap.primitiveType(), wrap.primitiveType()); + try { + mh = IMPL_LOOKUP.findStatic(ValueConversions.class, "identity", type); + } catch (NoAccessException ex) { + mh = null; + } + if (mh == null && wrap == Wrapper.VOID) { + mh = EMPTY; // #(){} : #()void + } + if (mh != null) { + cache.put(wrap, mh); + return mh; + } + + // use a raw conversion + if (wrap.isSingleWord() && wrap != Wrapper.INT) { + mh = retype(type, identity(Wrapper.INT)); + } else if (wrap.isDoubleWord() && wrap != Wrapper.LONG) { + mh = retype(type, identity(Wrapper.LONG)); + } + if (mh != null) { + cache.put(wrap, mh); + return mh; + } + throw new IllegalArgumentException("cannot find identity for " + wrap); + } + private static MethodHandle retype(MethodType type, MethodHandle mh) { return AdapterMethodHandle.makeRetypeOnly(IMPL_TOKEN, type, mh); } diff --git a/test/java/dyn/InvokeGenericTest.java b/test/java/dyn/InvokeGenericTest.java new file mode 100644 index 000000000..83aa10b02 --- /dev/null +++ b/test/java/dyn/InvokeGenericTest.java @@ -0,0 +1,484 @@ +/* + * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @summary unit tests for java.dyn.MethodHandle.invokeGeneric + * @compile -target 7 InvokeGenericTest.java + * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:+EnableMethodHandles test.java.dyn.InvokeGenericTest + */ + +package test.java.dyn; + +import java.dyn.*; +import static java.dyn.MethodHandles.*; +import static java.dyn.MethodType.*; +import java.lang.reflect.*; +import java.util.*; +import org.junit.*; +import static org.junit.Assert.*; +import static org.junit.Assume.*; + + +/** + * + * @author jrose + */ +public class InvokeGenericTest { + // How much output? + static int verbosity = 0; + static { + String vstr = System.getProperty("test.java.dyn.InvokeGenericTest.verbosity"); + if (vstr != null) verbosity = Integer.parseInt(vstr); + } + + @Test + public void testFirst() throws Throwable { + verbosity += 9; try { + // left blank for debugging + } finally { printCounts(); verbosity -= 9; } + } + + public InvokeGenericTest() { + } + + @Before + public void checkImplementedPlatform() { + boolean platformOK = false; + Properties properties = System.getProperties(); + String vers = properties.getProperty("java.vm.version"); + String name = properties.getProperty("java.vm.name"); + String arch = properties.getProperty("os.arch"); + if ((arch.equals("amd64") || arch.equals("i386") || arch.equals("x86") || + arch.equals("sparc") || arch.equals("sparcv9")) && + (name.contains("Client") || name.contains("Server")) + ) { + platformOK = true; + } else { + System.err.println("Skipping tests for unsupported platform: "+Arrays.asList(vers, name, arch)); + } + assumeTrue(platformOK); + } + + String testName; + static int allPosTests, allNegTests; + int posTests, negTests; + @After + public void printCounts() { + if (verbosity >= 2 && (posTests | negTests) != 0) { + System.out.println(); + if (posTests != 0) System.out.println("=== "+testName+": "+posTests+" positive test cases run"); + if (negTests != 0) System.out.println("=== "+testName+": "+negTests+" negative test cases run"); + allPosTests += posTests; + allNegTests += negTests; + posTests = negTests = 0; + } + } + void countTest(boolean positive) { + if (positive) ++posTests; + else ++negTests; + } + void countTest() { countTest(true); } + void startTest(String name) { + if (testName != null) printCounts(); + if (verbosity >= 1) + System.out.println(name); + posTests = negTests = 0; + testName = name; + } + + @BeforeClass + public static void setUpClass() throws Exception { + calledLog.clear(); + calledLog.add(null); + nextArgVal = INITIAL_ARG_VAL; + } + + @AfterClass + public static void tearDownClass() throws Exception { + int posTests = allPosTests, negTests = allNegTests; + if (verbosity >= 2 && (posTests | negTests) != 0) { + System.out.println(); + if (posTests != 0) System.out.println("=== "+posTests+" total positive test cases"); + if (negTests != 0) System.out.println("=== "+negTests+" total negative test cases"); + } + } + + static List calledLog = new ArrayList(); + static Object logEntry(String name, Object... args) { + return Arrays.asList(name, Arrays.asList(args)); + } + static Object called(String name, Object... args) { + Object entry = logEntry(name, args); + calledLog.add(entry); + return entry; + } + static void assertCalled(String name, Object... args) { + Object expected = logEntry(name, args); + Object actual = calledLog.get(calledLog.size() - 1); + if (expected.equals(actual) && verbosity < 9) return; + System.out.println("assertCalled "+name+":"); + System.out.println("expected: "+expected); + System.out.println("actual: "+actual); + System.out.println("ex. types: "+getClasses(expected)); + System.out.println("act. types: "+getClasses(actual)); + assertEquals("previous method call", expected, actual); + } + static void printCalled(MethodHandle target, String name, Object... args) { + if (verbosity >= 3) + System.out.println("calling MH="+target+" to "+name+Arrays.toString(args)); + } + + static Object castToWrapper(Object value, Class dst) { + Object wrap = null; + if (value instanceof Number) + wrap = castToWrapperOrNull(((Number)value).longValue(), dst); + if (value instanceof Character) + wrap = castToWrapperOrNull((char)(Character)value, dst); + if (wrap != null) return wrap; + return dst.cast(value); + } + + static Object castToWrapperOrNull(long value, Class dst) { + if (dst == int.class || dst == Integer.class) + return (int)(value); + if (dst == long.class || dst == Long.class) + return (long)(value); + if (dst == char.class || dst == Character.class) + return (char)(value); + if (dst == short.class || dst == Short.class) + return (short)(value); + if (dst == float.class || dst == Float.class) + return (float)(value); + if (dst == double.class || dst == Double.class) + return (double)(value); + if (dst == byte.class || dst == Byte.class) + return (byte)(value); + if (dst == boolean.class || dst == boolean.class) + return ((value % 29) & 1) == 0; + return null; + } + + static final int ONE_MILLION = (1000*1000), // first int value + TEN_BILLION = (10*1000*1000*1000), // scale factor to reach upper 32 bits + INITIAL_ARG_VAL = ONE_MILLION << 1; // <<1 makes space for sign bit; + static long nextArgVal; + static long nextArg(boolean moreBits) { + long val = nextArgVal++; + long sign = -(val & 1); // alternate signs + val >>= 1; + if (moreBits) + // Guarantee some bits in the high word. + // In any case keep the decimal representation simple-looking, + // with lots of zeroes, so as not to make the printed decimal + // strings unnecessarily noisy. + val += (val % ONE_MILLION) * TEN_BILLION; + return val ^ sign; + } + static int nextArg() { + // Produce a 32-bit result something like ONE_MILLION+(smallint). + // Example: 1_000_042. + return (int) nextArg(false); + } + static long nextArg(Class kind) { + if (kind == long.class || kind == Long.class || + kind == double.class || kind == Double.class) + // produce a 64-bit result something like + // ((TEN_BILLION+1) * (ONE_MILLION+(smallint))) + // Example: 10_000_420_001_000_042. + return nextArg(true); + return (long) nextArg(); + } + + static Object randomArg(Class param) { + Object wrap = castToWrapperOrNull(nextArg(param), param); + if (wrap != null) { + return wrap; + } +// import sun.dyn.util.Wrapper; +// Wrapper wrap = Wrapper.forBasicType(dst); +// if (wrap == Wrapper.OBJECT && Wrapper.isWrapperType(dst)) +// wrap = Wrapper.forWrapperType(dst); +// if (wrap != Wrapper.OBJECT) +// return wrap.wrap(nextArg++); + if (param.isInterface()) { + for (Class c : param.getClasses()) { + if (param.isAssignableFrom(c) && !c.isInterface()) + { param = c; break; } + } + } + if (param.isInterface() || param.isAssignableFrom(String.class)) + return "#"+nextArg(); + else + try { + return param.newInstance(); + } catch (InstantiationException ex) { + } catch (IllegalAccessException ex) { + } + return null; // random class not Object, String, Integer, etc. + } + static Object[] randomArgs(Class... params) { + Object[] args = new Object[params.length]; + for (int i = 0; i < args.length; i++) + args[i] = randomArg(params[i]); + return args; + } + static Object[] randomArgs(int nargs, Class param) { + Object[] args = new Object[nargs]; + for (int i = 0; i < args.length; i++) + args[i] = randomArg(param); + return args; + } + + static final Object ANON_OBJ = new Object(); + static Object zeroArg(Class param) { + Object x = castToWrapperOrNull(0L, param); + if (x != null) return x; + if (param.isInterface() || param.isAssignableFrom(String.class)) return "\"\""; + if (param == Object.class) return ANON_OBJ; + if (param.getComponentType() != null) return Array.newInstance(param.getComponentType(), 0); + return null; + } + static Object[] zeroArgs(Class... params) { + Object[] args = new Object[params.length]; + for (int i = 0; i < args.length; i++) + args[i] = zeroArg(params[i]); + return args; + } + static Object[] zeroArgs(List> params) { + return zeroArgs(params.toArray(new Class[0])); + } + + static T[] array(Class atype, E... a) { + return Arrays.copyOf(a, a.length, atype); + } + static T[] cat(T[] a, T... b) { + int alen = a.length, blen = b.length; + if (blen == 0) return a; + T[] c = Arrays.copyOf(a, alen + blen); + System.arraycopy(b, 0, c, alen, blen); + return c; + } + static Integer[] boxAll(int... vx) { + Integer[] res = new Integer[vx.length]; + for (int i = 0; i < res.length; i++) { + res[i] = vx[i]; + } + return res; + } + static Object getClasses(Object x) { + if (x == null) return x; + if (x instanceof String) return x; // keep the name + if (x instanceof List) { + // recursively report classes of the list elements + Object[] xa = ((List)x).toArray(); + for (int i = 0; i < xa.length; i++) + xa[i] = getClasses(xa[i]); + return Arrays.asList(xa); + } + return x.getClass().getSimpleName(); + } + + static MethodHandle changeArgTypes(MethodHandle target, Class argType) { + return changeArgTypes(target, 0, 999, argType); + } + static MethodHandle changeArgTypes(MethodHandle target, + int beg, int end, Class argType) { + MethodType targetType = target.type(); + end = Math.min(end, targetType.parameterCount()); + ArrayList> argTypes = new ArrayList>(targetType.parameterList()); + Collections.fill(argTypes.subList(beg, end), argType); + MethodType ttype2 = MethodType.methodType(targetType.returnType(), argTypes); + return MethodHandles.convertArguments(target, ttype2); + } + + // This lookup is good for all members in and under InvokeGenericTest. + static final Lookup LOOKUP = MethodHandles.lookup(); + + Map>, MethodHandle> CALLABLES = new HashMap>, MethodHandle>(); + MethodHandle callable(List> params) { + MethodHandle mh = CALLABLES.get(params); + if (mh == null) { + mh = collectArguments(collector_MH, methodType(Object.class, params)); + CALLABLES.put(params, mh); + } + return mh; + } + MethodHandle callable(Class... params) { + return callable(Arrays.asList(params)); + } + private static Object collector(Object... args) { + return Arrays.asList(args); + } + private static final MethodHandle collector_MH; + static { + try { + collector_MH + = LOOKUP.findStatic(LOOKUP.lookupClass(), + "collector", + methodType(Object.class, Object[].class)); + } catch (NoAccessException ex) { + throw new RuntimeException(ex); + } + } + + @Test + public void testSimple() throws Throwable { + startTest("testSimple"); + countTest(); + String[] args = { "one", "two" }; + MethodHandle mh = callable(Object.class, String.class); + Object res; List resl; + res = resl = (List) mh.invokeGeneric((String)args[0], (Object)args[1]); + //System.out.println(res); + assertEquals(Arrays.asList(args), res); + } + + @Test + public void testWrongArgumentCount() throws Throwable { + startTest("testWrongArgumentCount"); + for (int i = 0; i <= 10; i++) { + testWrongArgumentCount(Collections.>nCopies(i, Integer.class)); + if (i <= 4) { + testWrongArgumentCount(Collections.>nCopies(i, int.class)); + testWrongArgumentCount(Collections.>nCopies(i, long.class)); + } + } + } + public void testWrongArgumentCount(List> params) throws Throwable { + int max = params.size(); + for (int i = 0; i < max; i++) { + List> params2 = params.subList(0, i); + for (int k = 0; k <= 2; k++) { + if (k == 1) params = methodType(Object.class, params).generic().parameterList(); + if (k == 2) params2 = methodType(Object.class, params2).generic().parameterList(); + testWrongArgumentCount(params, params2); + testWrongArgumentCount(params2, params); + } + } + } + public void testWrongArgumentCount(List> expect, List> observe) throws Throwable { + countTest(false); + if (expect.equals(observe)) + assert(false); + MethodHandle target = callable(expect); + Object[] args = zeroArgs(observe); + Object junk; + try { + switch (args.length) { + case 0: + junk = target.invokeGeneric(); break; + case 1: + junk = target.invokeGeneric(args[0]); break; + case 2: + junk = target.invokeGeneric(args[0], args[1]); break; + case 3: + junk = target.invokeGeneric(args[0], args[1], args[2]); break; + case 4: + junk = target.invokeGeneric(args[0], args[1], args[2], args[3]); break; + default: + junk = MethodHandles.invokeVarargs(target, args); break; + } + } catch (WrongMethodTypeException ex) { + return; + } catch (Exception ex) { + throw new RuntimeException("wrong exception calling "+target+target.type()+" on "+Arrays.asList(args)+" : "+ex); + } + throw new RuntimeException("bad success calling "+target+target.type()+" on "+Arrays.asList(args)); + } + + /** Make a list of all combinations of the given types, with the given arities. + * A void return type is possible iff the first type is void.class. + */ + static List allMethodTypes(int minargc, int maxargc, Class... types) { + ArrayList result = new ArrayList(); + if (types.length > 0) { + ArrayList argcTypes = new ArrayList(); + // build arity-zero types first + for (Class rtype : types) { + argcTypes.add(MethodType.methodType(rtype)); + } + if (types[0] == void.class) + // void is not an argument type + types = Arrays.copyOfRange(types, 1, types.length); + for (int argc = 0; argc <= maxargc; argc++) { + if (argc >= minargc) + result.addAll(argcTypes); + if (argc >= maxargc) + break; + ArrayList prevTypes = argcTypes; + argcTypes = new ArrayList(); + for (MethodType prevType : prevTypes) { + for (Class ptype : types) { + argcTypes.add(prevType.insertParameterTypes(argc, ptype)); + } + } + } + } + return Collections.unmodifiableList(result); + } + static List allMethodTypes(int argc, Class... types) { + return allMethodTypes(argc, argc, types); + } + + interface RandomInterface { } + + MethodHandle toString_MH; + + @Test + public void testReferenceConversions() throws Throwable { + startTest("testReferenceConversions"); + toString_MH = LOOKUP. + findVirtual(Object.class, "toString", MethodType.methodType(String.class)); + String[] args = { "one", "two" }; + for (MethodType type : allMethodTypes(2, Object.class, String.class, RandomInterface.class)) { + testReferenceConversions(type, args); + } + } + public void testReferenceConversions(MethodType type, Object... args) throws Throwable { + countTest(); + if (verbosity > 3) System.out.println("target type: "+type); + MethodHandle mh = callable(type.parameterList()); + MethodHandle tsdrop = MethodHandles.dropArguments(toString_MH, 1, type.parameterList()); + mh = MethodHandles.foldArguments(tsdrop, mh); + mh = mh.asType(type); + Object res = mh.invokeGeneric((String)args[0], (Object)args[1]); + //System.out.println(res); + assertEquals(Arrays.asList(args).toString(), res); + } + + + @Test @Ignore("known failure pending 6939861") + public void testBoxConversions() throws Throwable { + startTest("testBoxConversions"); + countTest(); + Integer[] args = { 1, 2 }; + MethodHandle mh = callable(Object.class, int.class); + Object res; List resl; + res = resl = (List) mh.invokeGeneric((int)args[0], (Object)args[1]); + //System.out.println(res); + assertEquals(Arrays.asList(args), res); + } + +} diff --git a/test/java/dyn/MethodHandlesTest.java b/test/java/dyn/MethodHandlesTest.java index 62b0c9032..6986390c7 100644 --- a/test/java/dyn/MethodHandlesTest.java +++ b/test/java/dyn/MethodHandlesTest.java @@ -1472,7 +1472,7 @@ public class MethodHandlesTest { if (pos != 0) return; // can fold only at pos=0 for now countTest(); MethodHandle target = ValueConversions.varargsList(1 + nargs); - MethodHandle combine = ValueConversions.varargsList(fold); + MethodHandle combine = ValueConversions.varargsList(fold).asType(MethodType.genericMethodType(fold)); List argsToPass = Arrays.asList(randomArgs(nargs, Object.class)); if (verbosity >= 3) System.out.println("fold "+target+" with "+combine); -- GitLab