--- old/test/java/lang/invoke/ExplicitCastArgumentsTest.java 2015-07-31 19:31:23.355675803 +0300 +++ new/test/java/lang/invoke/ExplicitCastArgumentsTest.java 2015-07-31 19:31:23.127675798 +0300 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -21,79 +21,509 @@ * questions. */ -package java.lang.invoke; - +import java.io.File; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.WrongMethodTypeException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; import sun.invoke.util.Wrapper; +import com.oracle.testlibrary.jsr292.Helper; -/* @test +/* + * @test + * @bug 8060483 8066746 + * @key randomness + * @library /lib/testlibrary /lib/testlibrary/jsr292 * @summary unit tests for MethodHandles.explicitCastArguments() - * - * @run main/bootclasspath java.lang.invoke.ExplicitCastArgumentsTest + * @run main ExplicitCastArgumentsTest + */ + +/** + * Tests for MethodHandles.explicitCastArguments(). */ public class ExplicitCastArgumentsTest { - private static final boolean VERBOSE = Boolean.getBoolean("verbose"); + + 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(); + testNonBCPRef2Ref(); + testBCPRef2Ref(); + testReturnAny2Void(); + testReturnVoid2Any(); + testMultipleArgs(); System.out.println("TEST PASSED"); } - public static String[] f(String... args) { return args; } + /** + * 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"})); + 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"})); } - public static void testRef2Prim() throws Throwable { + /** + * 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; - testRef2Prim(from, to); + 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); + } } } } - public static void testRef2Prim(Wrapper from, Wrapper to) throws Throwable { - // 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. - test(from.wrapperType(), to.primitiveType(), null, false); + /** + * 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); + } + } + } + } } - public static void test(Class from, Class to, Object param, boolean failureExpected) throws Throwable { - if (VERBOSE) System.out.printf("%-10s => %-10s: %5s: ", from.getSimpleName(), to.getSimpleName(), param); + /** + * 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); + } + } + } + } - MethodHandle original = MethodHandles.identity(from); - MethodType newType = original.type().changeReturnType(to); + /** + * 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 testNonBCPRef2Ref() throws Throwable { + String testClassPath = System.getProperty("test.classes","."); + URL[] classpath = {(new File(testClassPath)).getCanonicalFile() + .toURI().toURL()}; + URLClassLoader ucl = URLClassLoader.newInstance(classpath); + Class testInterface = ucl.loadClass(THIS_CLASS.getSimpleName() + + "$TestInterface"); + Class testSuperClass = ucl.loadClass(THIS_CLASS.getSimpleName() + + "$TestSuperClass"); + Class testSubClass1 = ucl.loadClass(THIS_CLASS.getSimpleName() + + "$TestSubClass1"); + Class testSubClass2 = TestSubClass2.class; + Object testSuperObj = testSuperClass.newInstance(); + Object testObj01 = testSubClass1.newInstance(); + Object testObj02 = new TestSubClass2(); + Class[] parents = {testInterface, testSuperClass}; + Class[] children = {testSubClass1, testSubClass2}; + Object[] childInst = {testObj01, testObj02}; + for (TestConversionMode mode : TestConversionMode.values()) { + for (int i = 0; i < parents.length; i++) { + for (int j = 0; j < children.length; j++) { + // Child type to parent type non-null conversion, shoud succeed + testConversion(mode, children[i], parents[j], childInst[i], + childInst[i], false, null); + // Child type to parent type null conversion, shoud succeed + testConversion(mode, children[i], parents[j], null, + null, false, null); + // Parent type to child type non-null conversion with parent + // type instance, should fail + testConversion(mode, parents[i], + children[j], testSuperObj, null, true, + ClassCastException.class); + // Parent type to child type non-null conversion with child + // type instance, should succeed + testConversion(mode, parents[i], + children[j], childInst[j], childInst[j], false, null); + // Parent type to child type null conversion, should succeed + testConversion(mode, parents[i], + children[j], null, null, false, null); + } + // Parent type to child type non-null conversion with sibling + // type instance, should fail + testConversion(mode, parents[i], 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); + } + } + + /** + * Tests bootclasspath reference to reference conversions. + */ + public static void testBCPRef2Ref() { + 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 { - MethodHandle target = MethodHandles.explicitCastArguments(original, newType); - Object result = target.invokeWithArguments(param); + 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("%10s (%10s)", "'"+result+"'", result.getClass().getSimpleName()); + resultStr = String.format("Converted value and type are" + + " %10s (%10s)", "'" + result + "'", + result.getClass().getSimpleName()); } else { - resultStr = String.format("%10s", result); + resultStr = String.format("Converted value is %10s", result); } System.out.println(resultStr); } - if (failureExpected) { - String msg = String.format("No exception thrown: %s => %s; parameter: %s", from, to, param); + 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) { - String msg = String.format("Unexpected exception was thrown: %s => %s; parameter: %s", from, to, param); + 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); } } --- old/test/java/lang/invoke/LFCaching/TestMethods.java 2015-07-31 19:31:23.959675818 +0300 +++ new/test/java/lang/invoke/LFCaching/TestMethods.java 2015-07-31 19:31:23.811675814 +0300 @@ -610,26 +610,7 @@ * @return MethodType generated randomly. */ private static MethodType randomMethodTypeGenerator(int arity) { - final Class[] CLASSES = { - Object.class, - int.class, - boolean.class, - byte.class, - short.class, - char.class, - long.class, - float.class, - double.class - }; - if (arity > Helper.MAX_ARITY) { - throw new IllegalArgumentException( - String.format("Arity should not exceed %d!", Helper.MAX_ARITY)); - } - List> list = Helper.randomClasses(CLASSES, arity); - list = Helper.getParams(list, false, arity); - int i = Helper.RNG.nextInt(CLASSES.length + 1); - Class rtype = i == CLASSES.length ? void.class : CLASSES[i]; - return MethodType.methodType(rtype, list); + return Helper.randomMethodTypeGenerator(arity); } /** --- old/test/lib/testlibrary/jsr292/com/oracle/testlibrary/jsr292/Helper.java 2015-07-31 19:31:24.439675829 +0300 +++ new/test/lib/testlibrary/jsr292/com/oracle/testlibrary/jsr292/Helper.java 2015-07-31 19:31:24.251675825 +0300 @@ -315,4 +315,33 @@ } return null; } + + /** + * Routine used to obtain a randomly generated method type. + * + * @param arity Arity of returned method type. + * @return MethodType generated randomly. + */ + public static MethodType randomMethodTypeGenerator(int arity) { + final Class[] CLASSES = { + Object.class, + int.class, + boolean.class, + byte.class, + short.class, + char.class, + long.class, + float.class, + double.class + }; + if (arity > MAX_ARITY) { + throw new IllegalArgumentException( + String.format("Arity should not exceed %d!", MAX_ARITY)); + } + List> list = randomClasses(CLASSES, arity); + list = getParams(list, false, arity); + int i = RNG.nextInt(CLASSES.length + 1); + Class rtype = i == CLASSES.length ? void.class : CLASSES[i]; + return MethodType.methodType(rtype, list); + } }