< prev index next >

test/java/lang/invoke/ExplicitCastArgumentsTest.java

Print this page

        

@@ -1,7 +1,7 @@
 /*
- * 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
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.

@@ -19,83 +19,513 @@
  * 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 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<Wrapper, Object> 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);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 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 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);
+        }
     }
 
-    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);
+    /**
+     * Dummy method used in {@link #testReturnAny2Void} and
+     * {@link #testReturnVoid2Any} tests to form a method handle.
+     */
+    public static void retVoid() {}
 
-        MethodHandle original = MethodHandles.identity(from);
-        MethodType newType = original.type().changeReturnType(to);
+    /**
+     * 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<? extends Throwable> 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);
             }
         }
     }
 }
< prev index next >