# HG changeset patch # User psandoz # Date 1517426436 28800 # Node ID 8772acd913e5d6db528eb084e285d3477e564f8f # Parent c4d9d1b08e2e35257d605d8efb29b0fd726f852f 8187742: Minimal set of bootstrap methods for constant dynamic Reviewed-by: jrose, forax Contributed-by: brian.goetz@oracle.com, paul.sandoz@oracle.com diff -r c4d9d1b08e2e -r 8772acd913e5 src/java.base/share/classes/java/lang/invoke/ConstantBootstraps.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/java/lang/invoke/ConstantBootstraps.java Wed Jan 31 11:20:36 2018 -0800 @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2017, 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 java.lang.invoke; + +import sun.invoke.util.Wrapper; + +import static java.lang.invoke.MethodHandleNatives.mapLookupExceptionToError; +import static java.util.Objects.requireNonNull; + +/** + * Bootstrap methods for dynamically-computed constants. + * + *

The bootstrap methods in this class will throw a + * {@code NullPointerException} for any reference argument that is {@code null}, + * unless the argument is specified to be unused or specified to accept a + * {@code null} value. + * + * @since 10 + */ +public final class ConstantBootstraps { + // implements the upcall from the JVM, MethodHandleNatives.linkDynamicConstant: + /*non-public*/ + static Object makeConstant(MethodHandle bootstrapMethod, + // Callee information: + String name, Class type, + // Extra arguments for BSM, if any: + Object info, + // Caller information: + Class callerClass) { + // BSMI.invoke handles all type checking and exception translation. + // If type is not a reference type, the JVM is expecting a boxed + // version, and will manage unboxing on the other side. + return BootstrapMethodInvoker.invoke( + type, bootstrapMethod, name, type, info, callerClass); + } + + /** + * Returns a {@code null} object reference for the reference type specified + * by {@code type}. + * + * @param lookup unused + * @param name unused + * @param type a reference type + * @return a {@code null} value + * @throws IllegalArgumentException if {@code type} is not a reference type + */ + public static Object nullConstant(MethodHandles.Lookup lookup, String name, Class type) { + if (requireNonNull(type).isPrimitive()) { + throw new IllegalArgumentException(String.format("not reference: %s", type)); + } + + return null; + } + + /** + * Returns a {@link Class} mirror for the primitive type whose type + * descriptor is specified by {@code name}. + * + * @param lookup unused + * @param name the descriptor (JVMS 4.3) of the desired primitive type + * @param type the required result type (must be {@code Class.class}) + * @return the {@link Class} mirror + * @throws IllegalArgumentException if the name is not a descriptor for a + * primitive type or the type is not {@code Class.class} + */ + public static Class primitiveClass(MethodHandles.Lookup lookup, String name, Class type) { + requireNonNull(name); + requireNonNull(type); + if (type != Class.class) { + throw new IllegalArgumentException(); + } + if (name.length() == 0 || name.length() > 1) { + throw new IllegalArgumentException(String.format("not primitive: %s", name)); + } + + return Wrapper.forPrimitiveType(name.charAt(0)).primitiveType(); + } + + /** + * Returns an {@code enum} constant of the type specified by {@code type} + * with the name specified by {@code name}. + * + * @param lookup the lookup context describing the class performing the + * operation (normally stacked by the JVM) + * @param type the {@code Class} object describing the enum type for which + * a constant is to be returned + * @param name the name of the constant to return, which must exactly match + * an enum constant in the specified type. + * @param The enum type for which a constant value is to be returned + * @return the enum constant of the specified enum type with the + * specified name + * @throws IllegalAccessError if the declaring class or the field is not + * accessible to the class performing the operation + * @throws IllegalArgumentException if the specified enum type has + * no constant with the specified name, or the specified + * class object does not represent an enum type + * @see Enum#valueOf(Class, String) + */ + public static > E enumConstant(MethodHandles.Lookup lookup, String name, Class type) { + requireNonNull(lookup); + requireNonNull(name); + requireNonNull(type); + validateClassAccess(lookup, type); + + return Enum.valueOf(type, name); + } + + /** + * Returns the value of a static final field. + * + * @param lookup the lookup context describing the class performing the + * operation (normally stacked by the JVM) + * @param name the name of the field + * @param type the type of the field + * @param declaringClass the class in which the field is declared + * @return the value of the field + * @throws IllegalAccessError if the declaring class or the field is not + * accessible to the class performing the operation + * @throws NoSuchFieldError if the specified field does not exist + * @throws IncompatibleClassChangeError if the specified field is not + * {@code final} + */ + public static Object getStaticFinal(MethodHandles.Lookup lookup, String name, Class type, + Class declaringClass) { + requireNonNull(lookup); + requireNonNull(name); + requireNonNull(type); + requireNonNull(declaringClass); + + MethodHandle mh; + try { + mh = lookup.findStaticGetter(declaringClass, name, type); + MemberName member = mh.internalMemberName(); + if (!member.isFinal()) { + throw new IncompatibleClassChangeError("not a final field: " + name); + } + } + catch (ReflectiveOperationException ex) { + throw mapLookupExceptionToError(ex); + } + + // Since mh is a handle to a static field only instances of + // VirtualMachineError are anticipated to be thrown, such as a + // StackOverflowError or an InternalError from the j.l.invoke code + try { + return mh.invoke(); + } + catch (RuntimeException | Error e) { + throw e; + } + catch (Throwable e) { + throw new LinkageError("Unexpected throwable", e); + } + } + + /** + * Returns the value of a static final field declared in the class which + * is the same as the field's type (or, for primitive-valued fields, + * declared in the wrapper class.) This is a simplified form of + * {@link #getStaticFinal(MethodHandles.Lookup, String, Class, Class)} + * for the case where a class declares distinguished constant instances of + * itself. + * + * @param lookup the lookup context describing the class performing the + * operation (normally stacked by the JVM) + * @param name the name of the field + * @param type the type of the field + * @return the value of the field + * @throws IllegalAccessError if the declaring class or the field is not + * accessible to the class performing the operation + * @throws NoSuchFieldError if the specified field does not exist + * @throws IncompatibleClassChangeError if the specified field is not + * {@code final} + * @see #getStaticFinal(MethodHandles.Lookup, String, Class, Class) + */ + public static Object getStaticFinal(MethodHandles.Lookup lookup, String name, Class type) { + requireNonNull(type); + + Class declaring = type.isPrimitive() + ? Wrapper.forPrimitiveType(type).wrapperType() + : type; + return getStaticFinal(lookup, name, type, declaring); + } + + + /** + * Returns the result of invoking a method handle with the provided + * arguments. + * + * @param lookup the lookup context describing the class performing the + * operation (normally stacked by the JVM) + * @param name unused + * @param type the type of the value to be returned, which must be + * compatible with the return type of the method handle + * @param handle the method handle to be invoked + * @param args the arguments to pass to the method handle, as if with + * {@link MethodHandle#invokeWithArguments}. Each argument may be + * {@code null}. + * @return the result of invoking the method handle + * @throws WrongMethodTypeException if the handle's return type cannot be + * adjusted to the desired type + * @throws ClassCastException if an argument cannot be converted by + * reference casting + * @throws Throwable anything thrown by the method handle invocation + */ + public static Object invoke(MethodHandles.Lookup lookup, String name, Class type, + MethodHandle handle, Object... args) throws Throwable { + requireNonNull(type); + requireNonNull(handle); + requireNonNull(args); + + if (type != handle.type().returnType()) { + handle = handle.asType(handle.type().changeReturnType(type)); + } + + return handle.invokeWithArguments(args); + } + + /** + * Finds a {@link VarHandle} for an instance field. + * + * @param lookup the lookup context describing the class performing the + * operation (normally stacked by the JVM) + * @param name the name of the field + * @param type the required result type (must be {@code Class}) + * @param declaringClass the class in which the field is declared + * @param fieldType the type of the field + * @return the {@link VarHandle} + * @throws IllegalAccessError if the declaring class or the field is not + * accessible to the class performing the operation + * @throws NoSuchFieldError if the specified field does not exist + * @throws IllegalArgumentException if the type is not {@code VarHandle} + */ + public static VarHandle fieldVarHandle(MethodHandles.Lookup lookup, String name, Class type, + Class declaringClass, Class fieldType) { + requireNonNull(lookup); + requireNonNull(name); + requireNonNull(type); + requireNonNull(declaringClass); + requireNonNull(fieldType); + if (type != VarHandle.class) { + throw new IllegalArgumentException(); + } + + try { + return lookup.findVarHandle(declaringClass, name, fieldType); + } + catch (ReflectiveOperationException e) { + throw mapLookupExceptionToError(e); + } + } + + /** + * Finds a {@link VarHandle} for a static field. + * + * @param lookup the lookup context describing the class performing the + * operation (normally stacked by the JVM) + * @param name the name of the field + * @param type the required result type (must be {@code Class}) + * @param declaringClass the class in which the field is declared + * @param fieldType the type of the field + * @return the {@link VarHandle} + * @throws IllegalAccessError if the declaring class or the field is not + * accessible to the class performing the operation + * @throws NoSuchFieldError if the specified field does not exist + * @throws IllegalArgumentException if the type is not {@code VarHandle} + */ + public static VarHandle staticFieldVarHandle(MethodHandles.Lookup lookup, String name, Class type, + Class declaringClass, Class fieldType) { + requireNonNull(lookup); + requireNonNull(name); + requireNonNull(type); + requireNonNull(declaringClass); + requireNonNull(fieldType); + if (type != VarHandle.class) { + throw new IllegalArgumentException(); + } + + try { + return lookup.findStaticVarHandle(declaringClass, name, fieldType); + } + catch (ReflectiveOperationException e) { + throw mapLookupExceptionToError(e); + } + } + + /** + * Finds a {@link VarHandle} for an array type. + * + * @param lookup the lookup context describing the class performing the + * operation (normally stacked by the JVM) + * @param name unused + * @param type the required result type (must be {@code Class}) + * @param arrayClass the type of the array + * @return the {@link VarHandle} + * @throws IllegalAccessError if the component type of the array is not + * accessible to the class performing the operation + * @throws IllegalArgumentException if the type is not {@code VarHandle} + */ + public static VarHandle arrayVarHandle(MethodHandles.Lookup lookup, String name, Class type, + Class arrayClass) { + requireNonNull(lookup); + requireNonNull(type); + requireNonNull(arrayClass); + if (type != VarHandle.class) { + throw new IllegalArgumentException(); + } + + return MethodHandles.arrayElementVarHandle(validateClassAccess(lookup, arrayClass)); + } + + private static Class validateClassAccess(MethodHandles.Lookup lookup, Class type) { + try { + lookup.accessClass(type); + return type; + } + catch (ReflectiveOperationException ex) { + throw mapLookupExceptionToError(ex); + } + } +} diff -r c4d9d1b08e2e -r 8772acd913e5 src/java.base/share/classes/java/lang/invoke/DynamicConstant.java --- a/src/java.base/share/classes/java/lang/invoke/DynamicConstant.java Fri Sep 08 10:46:46 2017 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2017, 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 java.lang.invoke; - -/** - * Bootstrap methods for dynamically-computed constant. - */ -final class DynamicConstant { - // implements the upcall from the JVM, MethodHandleNatives.linkDynamicConstant: - /*non-public*/ - static Object makeConstant(MethodHandle bootstrapMethod, - // Callee information: - String name, Class type, - // Extra arguments for BSM, if any: - Object info, - // Caller information: - Class callerClass) { - // BSMI.invoke handles all type checking and exception translation. - // If type is not a reference type, the JVM is expecting a boxed - // version, and will manage unboxing on the other side. - return BootstrapMethodInvoker.invoke( - type, bootstrapMethod, name, type, info, callerClass); - } -} diff -r c4d9d1b08e2e -r 8772acd913e5 src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java Fri Sep 08 10:46:46 2017 -0700 +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java Wed Jan 31 11:20:36 2018 -0800 @@ -310,7 +310,7 @@ MethodHandle bootstrapMethod, String name, Class type, Object staticArguments) { - return DynamicConstant.makeConstant(bootstrapMethod, name, type, staticArguments, caller); + return ConstantBootstraps.makeConstant(bootstrapMethod, name, type, staticArguments, caller); } private static String staticArglistForTrace(Object staticArguments) { diff -r c4d9d1b08e2e -r 8772acd913e5 src/java.base/share/classes/sun/invoke/util/Wrapper.java --- a/src/java.base/share/classes/sun/invoke/util/Wrapper.java Fri Sep 08 10:46:46 2017 -0700 +++ b/src/java.base/share/classes/sun/invoke/util/Wrapper.java Wed Jan 31 11:20:36 2018 -0800 @@ -273,6 +273,25 @@ throw newIllegalArgumentException("not primitive: "+type); } + /** Return the wrapper that corresponds to the provided basic type char. + * The basic type char must be for one of the eight primitive types, or void. + * @throws IllegalArgumentException for unexpected types + */ + public static Wrapper forPrimitiveType(char basicTypeChar) { + switch (basicTypeChar) { + case 'I': return INT; + case 'J': return LONG; + case 'S': return SHORT; + case 'B': return BYTE; + case 'C': return CHAR; + case 'F': return FLOAT; + case 'D': return DOUBLE; + case 'Z': return BOOLEAN; + case 'V': return VOID; + default: throw newIllegalArgumentException("not primitive: " + basicTypeChar); + } + } + static Wrapper findPrimitiveType(Class type) { Wrapper w = FROM_PRIM[hashPrim(type)]; if (w != null && w.primitiveType == type) { diff -r c4d9d1b08e2e -r 8772acd913e5 test/jdk/java/lang/invoke/common/test/java/lang/invoke/lib/InstructionHelper.java --- a/test/jdk/java/lang/invoke/common/test/java/lang/invoke/lib/InstructionHelper.java Fri Sep 08 10:46:46 2017 -0700 +++ b/test/jdk/java/lang/invoke/common/test/java/lang/invoke/lib/InstructionHelper.java Wed Jan 31 11:20:36 2018 -0800 @@ -90,16 +90,30 @@ String name, Class type, String bsmMethodName, MethodType bsmType, Consumer> staticArgs) throws Exception { - return ldcDynamicConstant(l, name, cref(type), bsmMethodName, bsmType.toMethodDescriptorString(), staticArgs); + return ldcDynamicConstant(l, name, type, l.lookupClass(), bsmMethodName, bsmType, staticArgs); + } + + public static MethodHandle ldcDynamicConstant(MethodHandles.Lookup l, + String name, Class type, + Class bsmClass, String bsmMethodName, MethodType bsmType, + Consumer> staticArgs) throws Exception { + return ldcDynamicConstant(l, name, cref(type), csym(bsmClass), bsmMethodName, bsmType.toMethodDescriptorString(), staticArgs); } public static MethodHandle ldcDynamicConstant(MethodHandles.Lookup l, String name, String type, String bsmMethodName, String bsmType, Consumer> staticArgs) throws Exception { + return ldcDynamicConstant(l, name, type, csym(l.lookupClass()), bsmMethodName, bsmType, staticArgs); + } + + public static MethodHandle ldcDynamicConstant(MethodHandles.Lookup l, + String name, String type, + String bsmClass, String bsmMethodName, String bsmType, + Consumer> staticArgs) throws Exception { return ldc(l, type, P -> P.putDynamicConstant(name, type, - csym(l.lookupClass()), bsmMethodName, bsmType, + bsmClass, bsmMethodName, bsmType, staticArgs)); } diff -r c4d9d1b08e2e -r 8772acd913e5 test/jdk/java/lang/invoke/condy/ConstantBootstrapsTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/lang/invoke/condy/ConstantBootstrapsTest.java Wed Jan 31 11:20:36 2018 -0800 @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2017, 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. + * + * 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 + * @bug 8186046 + * @summary Test dynamic constant bootstraps + * @library /lib/testlibrary/bytecode /java/lang/invoke/common + * @build jdk.experimental.bytecode.BasicClassBuilder test.java.lang.invoke.lib.InstructionHelper + * @run testng ConstantBootstrapsTest + * @run testng/othervm -XX:+UnlockDiagnosticVMOptions -XX:UseBootstrapCallInfo=3 ConstantBootstrapsTest + */ + +import jdk.experimental.bytecode.PoolHelper; +import org.testng.annotations.Test; +import test.java.lang.invoke.lib.InstructionHelper; + +import java.lang.invoke.ConstantBootstraps; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandleInfo; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; +import java.lang.invoke.WrongMethodTypeException; +import java.math.BigInteger; +import java.util.List; +import java.util.Map; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; + +@Test +public class ConstantBootstrapsTest { + static final MethodHandles.Lookup L = MethodHandles.lookup(); + + static MethodType lookupMT(Class ret, Class... params) { + return MethodType.methodType(ret, MethodHandles.Lookup.class, String.class, Class.class). + appendParameterTypes(params); + } + + public void testNullConstant() throws Throwable { + var handle = InstructionHelper.ldcDynamicConstant(L, "_", Object.class, + ConstantBootstraps.class, "nullConstant", lookupMT(Object.class), + S -> {}); + assertNull(handle.invoke()); + + handle = InstructionHelper.ldcDynamicConstant(L, "_", MethodType.class, + ConstantBootstraps.class, "nullConstant", lookupMT(Object.class), + S -> {}); + assertNull(handle.invoke()); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testNullConstantPrimitiveClass() { + ConstantBootstraps.nullConstant(MethodHandles.lookup(), null, int.class); + } + + + public void testPrimitiveClass() throws Throwable { + var pm = Map.of( + "I", int.class, + "J", long.class, + "S", short.class, + "B", byte.class, + "C", char.class, + "F", float.class, + "D", double.class, + "Z", boolean.class, + "V", void.class + ); + + for (var desc : pm.keySet()) { + var handle = InstructionHelper.ldcDynamicConstant(L, desc, Class.class, + ConstantBootstraps.class, "primitiveClass", lookupMT(Class.class), + S -> {}); + assertEquals(handle.invoke(), pm.get(desc)); + } + } + + @Test(expectedExceptions = NullPointerException.class) + public void testPrimitiveClassNullName() { + ConstantBootstraps.primitiveClass(MethodHandles.lookup(), null, Class.class); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testPrimitiveClassNullType() { + ConstantBootstraps.primitiveClass(MethodHandles.lookup(), "I", null); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testPrimitiveClassEmptyName() { + ConstantBootstraps.primitiveClass(MethodHandles.lookup(), "", Class.class); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testPrimitiveClassWrongNameChar() { + ConstantBootstraps.primitiveClass(MethodHandles.lookup(), "L", Class.class); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testPrimitiveClassWrongNameString() { + ConstantBootstraps.primitiveClass(MethodHandles.lookup(), "Ljava/lang/Object;", Class.class); + } + + + public void testEnumConstant() throws Throwable { + for (var v : StackWalker.Option.values()) { + var handle = InstructionHelper.ldcDynamicConstant(L, v.name(), StackWalker.Option.class, + ConstantBootstraps.class, "enumConstant", lookupMT(Enum.class), + S -> { }); + assertEquals(handle.invoke(), v); + } + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testEnumConstantUnknown() { + ConstantBootstraps.enumConstant(MethodHandles.lookup(), "DOES_NOT_EXIST", StackWalker.Option.class); + } + + + public void testGetStaticDecl() throws Throwable { + var handle = InstructionHelper.ldcDynamicConstant(L, "TYPE", Class.class, + ConstantBootstraps.class, "getStaticFinal", lookupMT(Object.class, Class.class), + S -> { S.add("java/lang/Integer", PoolHelper::putClass); }); + assertEquals(handle.invoke(), int.class); + } + + public void testGetStaticSelf() throws Throwable { + var handle = InstructionHelper.ldcDynamicConstant(L, "MAX_VALUE", int.class, + ConstantBootstraps.class, "getStaticFinal", lookupMT(Object.class), + S -> { }); + assertEquals(handle.invoke(), Integer.MAX_VALUE); + + + handle = InstructionHelper.ldcDynamicConstant(L, "ZERO", BigInteger.class, + ConstantBootstraps.class, "getStaticFinal", lookupMT(Object.class), + S -> { }); + assertEquals(handle.invoke(), BigInteger.ZERO); + } + + + public void testInvoke() throws Throwable { + var handle = InstructionHelper.ldcDynamicConstant( + L, "_", List.class, + ConstantBootstraps.class, "invoke", lookupMT(Object.class, MethodHandle.class, Object[].class), + S -> { + S.add("", (P, Z) -> { + return P.putHandle(MethodHandleInfo.REF_invokeStatic, "java/util/List", "of", + MethodType.methodType(List.class, Object[].class).toMethodDescriptorString(), + true); + }); + S.add(1).add(2).add(3).add(4); + }); + assertEquals(handle.invoke(), List.of(1, 2, 3, 4)); + } + + public void testInvokeAsType() throws Throwable { + var handle = InstructionHelper.ldcDynamicConstant( + L, "_", int.class, + ConstantBootstraps.class, "invoke", lookupMT(Object.class, MethodHandle.class, Object[].class), + S -> { + S.add("", (P, Z) -> { + return P.putHandle(MethodHandleInfo.REF_invokeStatic, "java/lang/Integer", "valueOf", + MethodType.methodType(Integer.class, String.class).toMethodDescriptorString(), + false); + }); + S.add("42"); + }); + assertEquals(handle.invoke(), 42); + } + + @Test(expectedExceptions = ClassCastException.class) + public void testInvokeAsTypeClassCast() throws Throwable { + ConstantBootstraps.invoke(MethodHandles.lookup(), "_", String.class, + MethodHandles.lookup().findStatic(Integer.class, "valueOf", MethodType.methodType(Integer.class, String.class)), + "42"); + } + + @Test(expectedExceptions = WrongMethodTypeException.class) + public void testInvokeAsTypeWrongReturnType() throws Throwable { + ConstantBootstraps.invoke(MethodHandles.lookup(), "_", short.class, + MethodHandles.lookup().findStatic(Integer.class, "parseInt", MethodType.methodType(int.class, String.class)), + "42"); + } + + + static class X { + public String f; + public static String sf; + } + + public void testVarHandleField() throws Throwable { + var handle = InstructionHelper.ldcDynamicConstant( + L, "f", VarHandle.class, + ConstantBootstraps.class, "fieldVarHandle", lookupMT(VarHandle.class, Class.class, Class.class), + S -> { + S.add(X.class.getName().replace('.', '/'), PoolHelper::putClass). + add("java/lang/String", PoolHelper::putClass); + }); + + var vhandle = (VarHandle) handle.invoke(); + assertEquals(vhandle.varType(), String.class); + assertEquals(vhandle.coordinateTypes(), List.of(X.class)); + } + + public void testVarHandleStaticField() throws Throwable { + var handle = InstructionHelper.ldcDynamicConstant( + L, "sf", VarHandle.class, + ConstantBootstraps.class, "staticFieldVarHandle", lookupMT(VarHandle.class, Class.class, Class.class), + S -> { + S.add(X.class.getName().replace('.', '/'), PoolHelper::putClass). + add("java/lang/String", PoolHelper::putClass); + }); + + var vhandle = (VarHandle) handle.invoke(); + assertEquals(vhandle.varType(), String.class); + assertEquals(vhandle.coordinateTypes(), List.of()); + } + + public void testVarHandleArray() throws Throwable { + var handle = InstructionHelper.ldcDynamicConstant( + L, "_", VarHandle.class, + ConstantBootstraps.class, "arrayVarHandle", lookupMT(VarHandle.class, Class.class), + S -> { + S.add(String[].class.getName().replace('.', '/'), PoolHelper::putClass); + }); + + var vhandle = (VarHandle) handle.invoke(); + assertEquals(vhandle.varType(), String.class); + assertEquals(vhandle.coordinateTypes(), List.of(String[].class, int.class)); + } +} diff -r c4d9d1b08e2e -r 8772acd913e5 test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/BytePoolHelper.java --- a/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/BytePoolHelper.java Fri Sep 08 10:46:46 2017 -0700 +++ b/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/BytePoolHelper.java Wed Jan 31 11:20:36 2018 -0800 @@ -490,7 +490,8 @@ BsmKey poolKey = bootstraps.lookup(bsmKey); if (poolKey == null) { poolKey = bsmKey.dup(); - int bsm_ref = putHandleInternal(MethodHandleInfo.REF_invokeStatic, bsmClass, bsmName, bsmType); + // TODO the BSM could be a static method on an interface + int bsm_ref = putHandleInternal(MethodHandleInfo.REF_invokeStatic, bsmClass, bsmName, bsmType, false); poolKey.at(currentBsmIndex++); bootstraps.enter(poolKey); bsm_attr.writeChar(bsm_ref); @@ -562,15 +563,20 @@ @Override public int putHandle(int refKind, S owner, CharSequence name, T type) { - return putHandleInternal(refKind, symbolToString.apply(owner), name, typeToString.apply(type)); + return putHandleInternal(refKind, symbolToString.apply(owner), name, typeToString.apply(type), false); } - private int putHandleInternal(int refKind, String owner, CharSequence name, String type) { + @Override + public int putHandle(int refKind, S owner, CharSequence name, T type, boolean isInterface) { + return putHandleInternal(refKind, symbolToString.apply(owner), name, typeToString.apply(type), isInterface); + } + + private int putHandleInternal(int refKind, String owner, CharSequence name, String type, boolean isInterface) { key.setMethodHandle(refKind, owner, name, type); PoolKey poolKey = entries.lookup(key); if (poolKey == null) { poolKey = key.dup(); - int ref_idx = putMemberRefInternal(fromKind(refKind), owner, name, type); + int ref_idx = putMemberRefInternal(fromKind(refKind, isInterface), owner, name, type); poolKey.at(currentIndex++); entries.enter(poolKey); pool.writeByte(PoolTag.CONSTANT_METHODHANDLE.tag); @@ -580,7 +586,7 @@ return poolKey.index; } - PoolTag fromKind(int bsmKind) { + PoolTag fromKind(int bsmKind, boolean isInterface) { switch (bsmKind) { case 1: // REF_getField case 2: // REF_getStatic @@ -591,9 +597,8 @@ case 6: // REF_invokeStatic case 7: // REF_invokeSpecial case 8: // REF_newInvokeSpecial - return PoolTag.CONSTANT_METHODREF; case 9: // REF_invokeInterface - return PoolTag.CONSTANT_INTERFACEMETHODREF; + return isInterface ? PoolTag.CONSTANT_INTERFACEMETHODREF : PoolTag.CONSTANT_METHODREF; default: throw new IllegalStateException(); } diff -r c4d9d1b08e2e -r 8772acd913e5 test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/IsolatedMethodBuilder.java --- a/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/IsolatedMethodBuilder.java Fri Sep 08 10:46:46 2017 -0700 +++ b/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/IsolatedMethodBuilder.java Wed Jan 31 11:20:36 2018 -0800 @@ -117,6 +117,11 @@ } @Override + public int putHandle(int refKind, Class owner, CharSequence name, String type, boolean isInterface) { + return 0; //??? + } + + @Override public int putMethodType(String s) { return 0; //??? } diff -r c4d9d1b08e2e -r 8772acd913e5 test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/PoolHelper.java --- a/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/PoolHelper.java Fri Sep 08 10:46:46 2017 -0700 +++ b/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/PoolHelper.java Wed Jan 31 11:20:36 2018 -0800 @@ -58,6 +58,8 @@ int putHandle(int refKind, S owner, CharSequence name, T type); + int putHandle(int refKind, S owner, CharSequence name, T type, boolean isInterface); + int putInvokeDynamic(CharSequence invokedName, T invokedType, S bsmClass, CharSequence bsmName, T bsmType, Consumer> staticArgs); int putDynamicConstant(CharSequence constName, T constType, S bsmClass, CharSequence bsmName, T bsmType, Consumer> staticArgs); diff -r c4d9d1b08e2e -r 8772acd913e5 test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/TypedCodeBuilder.java --- a/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/TypedCodeBuilder.java Fri Sep 08 10:46:46 2017 -0700 +++ b/test/jdk/lib/testlibrary/bytecode/jdk/experimental/bytecode/TypedCodeBuilder.java Wed Jan 31 11:20:36 2018 -0800 @@ -1033,6 +1033,12 @@ } @Override + public int putHandle(int refKind, S owner, CharSequence name, T t, boolean isInterface) { + type = typeHelper.type(typeHelper.symbolFrom("java/lang/invoke/MethodHandle")); + return poolHelper.putHandle(refKind, owner, name, t, isInterface); + } + + @Override public int putInvokeDynamic(CharSequence invokedName, T invokedType, S bsmClass, CharSequence bsmName, T bsmType, Consumer> staticArgs) { throw new IllegalStateException(); }