/* * Copyright (c) 2014, 2015, 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. */ import com.oracle.testlibrary.jsr292.Helper; import java.io.File; import java.io.Serializable; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.invoke.WrongMethodTypeException; import java.util.HashMap; import java.util.Map; import java.util.Random; import sun.invoke.util.Wrapper; /* * @test * @bug 8060483 8066746 * @key randomness * @library /lib/testlibrary /lib/testlibrary/jsr292 * @summary unit tests for MethodHandles.explicitCastArguments() * @run main ExplicitCastArgumentsTest */ /** * Tests for MethodHandles.explicitCastArguments(). */ public class ExplicitCastArgumentsTest { private static final boolean VERBOSE = Helper.IS_VERBOSE; private static final Class THIS_CLASS = ExplicitCastArgumentsTest.class; private static final Random RNG = Helper.RNG; private static final Map RANDOM_VALUES = new HashMap<>(9); static { RANDOM_VALUES.put(Wrapper.BOOLEAN, RNG.nextBoolean()); RANDOM_VALUES.put(Wrapper.BYTE, (byte) RNG.nextInt()); RANDOM_VALUES.put(Wrapper.SHORT, (short) RNG.nextInt()); RANDOM_VALUES.put(Wrapper.CHAR, (char) RNG.nextInt()); RANDOM_VALUES.put(Wrapper.INT, RNG.nextInt()); RANDOM_VALUES.put(Wrapper.LONG, RNG.nextLong()); RANDOM_VALUES.put(Wrapper.FLOAT, RNG.nextFloat()); RANDOM_VALUES.put(Wrapper.DOUBLE, RNG.nextDouble()); RANDOM_VALUES.put(Wrapper.OBJECT, new Object()); } public static void main(String[] args) throws Throwable { testVarargsCollector(); testNullRef2Prim(); testRef2Prim(); testPrim2Ref(); testPrim2Prim(); testNonBCPRef2NonBCPRef(); testBCPRef2BCPRef(); testNonBCPRef2BCPRef(); testReturnAny2Void(); testReturnVoid2Any(); testMultipleArgs(); System.out.println("TEST PASSED"); } /** * Dummy method used in {@link #testVarargsCollector} test to form a method * handle. * * @param args - any args * @return - returns args */ public static String[] f(String... args) { return args; } /** * Tests that MHs.explicitCastArguments does incorrect type checks for * VarargsCollector. Bug 8066746. * * @throws java.lang.Throwable */ public static void testVarargsCollector() throws Throwable { MethodType mt = MethodType.methodType(String[].class, String[].class); MethodHandle mh = MethodHandles.publicLookup() .findStatic(THIS_CLASS, "f", mt); mh = MethodHandles.explicitCastArguments(mh, MethodType.methodType(Object.class, Object.class)); mh.invokeWithArguments((Object) (new String[]{"str1", "str2"})); } /** * Tests that null wrapper reference is successfully converted to primitive * types. Converted result should be zero for a primitive. Bug 8060483. */ public static void testNullRef2Prim() { for (Wrapper from : Wrapper.values()) { for (Wrapper to : Wrapper.values()) { if (from == Wrapper.VOID || to == Wrapper.VOID) { continue; } // MHs.eCA javadoc: // If T0 is a reference and T1 a primitive, and if the reference // is null at runtime, a zero value is introduced. for (TestConversionMode mode : TestConversionMode.values()) { testConversion(mode, from.wrapperType(), to.primitiveType(), null, to.zero(), false, null); } } } } /** * Tests that non-null wrapper reference is successfully converted to * primitive types. */ public static void testRef2Prim() { for (Wrapper from : Wrapper.values()) { for (Wrapper to : Wrapper.values()) { if (from == Wrapper.VOID || to == Wrapper.VOID || to == Wrapper.OBJECT) { continue; } Object value = RANDOM_VALUES.get(from); for (TestConversionMode mode : TestConversionMode.values()) { if (from != Wrapper.OBJECT) { Object convValue = to.wrap(value); testConversion(mode, from.wrapperType(), to.primitiveType(), value, convValue, false, null); } else { testConversion(mode, from.wrapperType(), to.primitiveType(), value, null, true, ClassCastException.class); } } } } } /** * Tests that primitive is successfully converted to wrapper reference * types, to the Number type (if possible) and to the Object type. */ public static void testPrim2Ref() { for (Wrapper from : Wrapper.values()) { for (Wrapper to : Wrapper.values()) { if (from == Wrapper.VOID || from == Wrapper.OBJECT || to == Wrapper.VOID || to == Wrapper.OBJECT) { continue; } Object value = RANDOM_VALUES.get(from); for (TestConversionMode mode : TestConversionMode.values()) { if (from == to) { testConversion(mode, from.primitiveType(), to.wrapperType(), value, value, false, null); } else { testConversion(mode, from.primitiveType(), to.wrapperType(), value, null, true, ClassCastException.class); } if (from != Wrapper.BOOLEAN && from != Wrapper.CHAR) { testConversion(mode, from.primitiveType(), Number.class, value, value, false, null); } else { testConversion(mode, from.primitiveType(), Number.class, value, null, true, ClassCastException.class); } testConversion(mode, from.primitiveType(), Object.class, value, value, false, null); } } } } /** * Tests that primitive is successfully converted to other primitive type. */ public static void testPrim2Prim() { for (Wrapper from : Wrapper.values()) { for (Wrapper to : Wrapper.values()) { if (from == Wrapper.VOID || to == Wrapper.VOID || from == Wrapper.OBJECT || to == Wrapper.OBJECT) { continue; } Object value = RANDOM_VALUES.get(from); Object convValue = to.wrap(value); for (TestConversionMode mode : TestConversionMode.values()) { testConversion(mode, from.primitiveType(), to.primitiveType(), value, convValue, false, null); } } } } /** * Dummy interface for {@link #testNonBCPRef2Ref} test. */ public static interface TestInterface {} /** * Dummy class for {@link #testNonBCPRef2Ref} test. */ public static class TestSuperClass implements TestInterface {} /** * Dummy class for {@link #testNonBCPRef2Ref} test. */ public static class TestSubClass1 extends TestSuperClass {} /** * Dummy class for {@link #testNonBCPRef2Ref} test. */ public static class TestSubClass2 extends TestSuperClass {} /** * Tests non-bootclasspath reference to reference conversions. * * @throws java.lang.Throwable */ public static void testNonBCPRef2NonBCPRef() throws Throwable { Class testInterface = TestInterface.class; Class testSuperClass = TestSuperClass.class; Class testSubClass1 = TestSubClass1.class; Class testSubClass2 = TestSubClass2.class; Object testSuperObj = new TestSuperClass(); Object testObj01 = new TestSubClass1(); Object testObj02 = new TestSubClass2(); Class[] parents = {testInterface, testSuperClass}; Class[] children = {testSubClass1, testSubClass2}; Object[] childInst = {testObj01, testObj02}; for (TestConversionMode mode : TestConversionMode.values()) { for (Class parent : parents) { for (int j = 0; j < children.length; j++) { // Child type to parent type non-null conversion, shoud succeed testConversion(mode, children[j], parent, childInst[j], childInst[j], false, null); // Child type to parent type null conversion, shoud succeed testConversion(mode, children[j], parent, null, null, false, null); // Parent type to child type non-null conversion with parent // type instance, should fail testConversion(mode, parent, children[j], testSuperObj, null, true, ClassCastException.class); // Parent type to child type non-null conversion with child // type instance, should succeed testConversion(mode, parent, children[j], childInst[j], childInst[j], false, null); // Parent type to child type null conversion, should succeed testConversion(mode, parent, children[j], null, null, false, null); } // Parent type to child type non-null conversion with sibling // type instance, should fail testConversion(mode, parent, testSubClass1, testObj02, null, true, ClassCastException.class); } // Sibling type non-null conversion, should fail testConversion(mode, testSubClass1, testSubClass2, testObj01, null, true, ClassCastException.class); // Sibling type null conversion, should succeed testConversion(mode, testSubClass1, testSubClass2, null, null, false, null); } } /** * Dummy interface for {@link #testNonBCPRef2BCPRef} test. */ public static interface TestSerializableInterface extends Serializable {} /** * Dummy class for {@link #testNonBCPRef2BCPRef} test. */ public static class TestSerializableClass implements TestSerializableInterface {} /** * Dummy class for {@link #testNonBCPRef2BCPRef} test. */ public static class TestFileChildClass extends File implements TestSerializableInterface { public TestFileChildClass(String pathname) { super(pathname); } } /** * Tests non-bootclasspath reference to bootclasspath reference conversions * and vice-versa. * * @throws java.lang.Throwable */ public static void testNonBCPRef2BCPRef() throws Throwable { Class bcpInterface = Serializable.class; Class bcpSuperClass = File.class; Class nonBcpInterface = TestSerializableInterface.class; Class nonBcpSuperSiblingClass = TestSerializableClass.class; Class nonBcpSubClass = TestFileChildClass.class; Object bcpSuperObj = new File("."); Object testSuperSiblingObj = new TestSerializableClass(); Object testSubObj = new TestFileChildClass("."); Class[] parents = {bcpInterface, bcpSuperClass}; for (TestConversionMode mode : TestConversionMode.values()) { for (Class parent : parents) { // Child type to parent type non-null conversion, shoud succeed testConversion(mode, nonBcpSubClass, parent, testSubObj, testSubObj, false, null); // Child type to parent type null conversion, shoud succeed testConversion(mode, nonBcpSubClass, parent, null, null, false, null); // Parent type to child type non-null conversion with parent // type instance, should fail testConversion(mode, parent, nonBcpSubClass, bcpSuperObj, null, true, ClassCastException.class); // Parent type to child type non-null conversion with child // type instance, should succeed testConversion(mode, parent, nonBcpSubClass, testSubObj, testSubObj, false, null); // Parent type to child type null conversion, should succeed testConversion(mode, parent, nonBcpSubClass, null, null, false, null); } // Parent type to child type non-null conversion with // super sibling type instance, should fail testConversion(mode, bcpInterface, nonBcpSubClass, testSuperSiblingObj, null, true, ClassCastException.class); Class[] siblings = {nonBcpSubClass, bcpSuperClass}; for (Class sibling : siblings) { // Non-bcp class to bcp/non-bcp sibling class non-null // conversion with nonBcpSuperSiblingClass instance, should fail testConversion(mode, nonBcpSuperSiblingClass, sibling, testSuperSiblingObj, null, true, ClassCastException.class); // Non-bcp class to bcp/non-bcp sibling class null conversion, // should succeed testConversion(mode, nonBcpSuperSiblingClass, sibling, null, null, false, null); // Non-bcp interface to bcp/non-bcp sibling class non-null // conversion with nonBcpSubClass instance, should succeed testConversion(mode, nonBcpInterface, sibling, testSubObj, testSubObj, false, null); // Non-bcp interface to bcp/non-bcp sibling class // null conversion, should succeed testConversion(mode, nonBcpInterface, sibling, null, null, false, null); // Non-bcp interface to bcp/non-bcp sibling class non-null // conversion with nonBcpSuperSiblingClass instance, should fail testConversion(mode, nonBcpInterface, sibling, testSuperSiblingObj, testSubObj, true, ClassCastException.class); } } } /** * Tests bootclasspath reference to reference conversions. */ public static void testBCPRef2BCPRef() { Class bcpInterface = CharSequence.class; Class bcpSubClass1 = String.class; Class bcpSubClass2 = StringBuffer.class; Object testObj01 = new String("test"); Object testObj02 = new StringBuffer("test"); Class[] children = {bcpSubClass1, bcpSubClass2}; Object[] childInst = {testObj01, testObj02}; for (TestConversionMode mode : TestConversionMode.values()) { for (int i = 0; i < children.length; i++) { // Child type to parent type non-null conversion, shoud succeed testConversion(mode, children[i], bcpInterface, childInst[i], childInst[i], false, null); // Child type to parent type null conversion, shoud succeed testConversion(mode, children[i], bcpInterface, null, null, false, null); // Parent type to child type non-null conversion with child // type instance, should succeed testConversion(mode, bcpInterface, children[i], childInst[i], childInst[i], false, null); // Parent type to child type null conversion, should succeed testConversion(mode, bcpInterface, children[i], null, null, false, null); } // Sibling type non-null conversion, should fail testConversion(mode, bcpSubClass1, bcpSubClass2, testObj01, null, true, ClassCastException.class); // Sibling type null conversion, should succeed testConversion(mode, bcpSubClass1, bcpSubClass2, null, null, false, null); // Parent type to child type non-null conversion with sibling // type instance, should fail testConversion(mode, bcpInterface, bcpSubClass1, testObj02, null, true, ClassCastException.class); } } /** * Dummy method used in {@link #testReturnAny2Void} and * {@link #testReturnVoid2Any} tests to form a method handle. */ public static void retVoid() {} /** * Tests that non-null any return is successfully converted to non-type * void. */ public static void testReturnAny2Void() { for (Wrapper from : Wrapper.values()) { testConversion(TestConversionMode.RETURN_VALUE, from.wrapperType(), void.class, RANDOM_VALUES.get(from), null, false, null); testConversion(TestConversionMode.RETURN_VALUE, from.primitiveType(), void.class, RANDOM_VALUES.get(from), null, false, null); } } /** * Tests that void return is successfully converted to primitive and * reference. Result should be zero for primitives and null for references. */ public static void testReturnVoid2Any() { for (Wrapper to : Wrapper.values()) { testConversion(TestConversionMode.RETURN_VALUE, void.class, to.primitiveType(), null, to.zero(), false, null); testConversion(TestConversionMode.RETURN_VALUE, void.class, to.wrapperType(), null, null, false, null); } } private static void checkForWrongMethodTypeException(MethodHandle mh, MethodType mt) { try { MethodHandles.explicitCastArguments(mh, mt); throw new AssertionError("Expected WrongMethodTypeException is not thrown"); } catch (WrongMethodTypeException wmte) { if (VERBOSE) { System.out.printf("Expected exception %s: %s\n", wmte.getClass(), wmte.getMessage()); } } } /** * Tests that MHs.eCA method works correctly with MHs with multiple arguments. * @throws Throwable */ public static void testMultipleArgs() throws Throwable { int arity = 1 + RNG.nextInt(Helper.MAX_ARITY / 2 - 2); int arityMinus = RNG.nextInt(arity); int arityPlus = arity + RNG.nextInt(Helper.MAX_ARITY / 2 - arity) + 1; MethodType mType = Helper.randomMethodTypeGenerator(arity); MethodType mTypeNew = Helper.randomMethodTypeGenerator(arity); MethodType mTypeNewMinus = Helper.randomMethodTypeGenerator(arityMinus); MethodType mTypeNewPlus = Helper.randomMethodTypeGenerator(arityPlus); Class rType = mType.returnType(); MethodHandle original; if (rType.equals(void.class)) { MethodType mt = MethodType.methodType(void.class); original = MethodHandles.publicLookup() .findStatic(THIS_CLASS, "retVoid", mt); } else { Object rValue = Helper.castToWrapper(1, rType); original = MethodHandles.constant(rType, rValue); } original = Helper.addTrailingArgs(original, arity, mType.parameterList()); MethodHandle target = MethodHandles .explicitCastArguments(original, mTypeNew); Object[] parList = Helper.randomArgs(mTypeNew.parameterList()); for (int i = 0; i < parList.length; i++) { if (parList[i] instanceof String) { parList[i] = null; //getting rid of Stings produced by randomArgs } } target.invokeWithArguments(parList); checkForWrongMethodTypeException(original, mTypeNewMinus); checkForWrongMethodTypeException(original, mTypeNewPlus); } /** * Enumeration of test conversion modes. */ public enum TestConversionMode { RETURN_VALUE, ARGUMENT; } /** * Tests type and value conversion. Comparing with the given expected result. * * @param mode - test conversion mode. See {@link #TestConversionMode}. * @param from - source type. * @param to - destination type. * @param param - value to be converted. * @param expectedResult - expected value after conversion. * @param failureExpected - true if conversion failure expected. * @param expectedException - expected exception class if * {@code failureExpected} is true. */ public static void testConversion(TestConversionMode mode, Class from, Class to, Object param, Object expectedResult, boolean failureExpected, Class expectedException) { if (VERBOSE) { System.out.printf("Testing return value conversion: " + "%-10s => %-10s: %5s: ", from.getSimpleName(), to.getSimpleName(), param); } MethodHandle original = null; MethodType newType = null; switch (mode) { case RETURN_VALUE: if (from.equals(void.class)) { MethodType mt = MethodType.methodType(void.class); try { original = MethodHandles.publicLookup() .findStatic(THIS_CLASS, "retVoid", mt); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new Error("Unexpected issue", ex); } } else { original = MethodHandles.constant(from, param); } newType = original.type().changeReturnType(to); break; case ARGUMENT: if (from.equals(void.class) || to.equals(void.class)) { throw new Error("Test issue: argument conversion does not" + " work with non-type void"); } original = MethodHandles.identity(to); newType = original.type().changeParameterType(0, from); break; default: String msg = String.format("Test issue: unknown test" + " convertion mode %s.", mode.name()); throw new Error(msg); } try { MethodHandle target = MethodHandles .explicitCastArguments(original, newType); Object result; switch (mode) { case RETURN_VALUE: result = target.invokeWithArguments(); break; case ARGUMENT: result = target.invokeWithArguments(param); break; default: String msg = String.format("Test issue: unknown test" + " convertion mode %s.", mode.name()); throw new Error(msg); } if (!failureExpected && (expectedResult != null && !expectedResult.equals(result) || expectedResult == null && result != null)) { String msg = String.format("Conversion result %s is not equal" + " to the expected result %10s", result, expectedResult); throw new AssertionError(msg); } if (VERBOSE) { String resultStr; if (result != null) { resultStr = String.format("Converted value and type are" + " %10s (%10s)", "'" + result + "'", result.getClass().getSimpleName()); } else { resultStr = String.format("Converted value is %10s", result); } System.out.println(resultStr); } if (failureExpected) { String msg = String.format("No exception thrown while testing" + " return value conversion: %10s => %10s;" + " parameter: %10s", from, to, param); throw new AssertionError(msg); } } catch (AssertionError e) { throw e; // report test failure } catch (Throwable e) { if (VERBOSE) { System.out.printf("%s: %s\n", e.getClass(), e.getMessage()); } if (!failureExpected || !e.getClass().equals(expectedException)) { String msg = String.format("Unexpected exception was thrown" + " while testing return value conversion:" + " %s => %s; parameter: %s", from, to, param); throw new AssertionError(msg, e); } } } }