1 /*
   2  * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.io.File;
  25 import java.lang.invoke.MethodHandle;
  26 import java.lang.invoke.MethodHandles;
  27 import java.lang.invoke.MethodType;
  28 import java.lang.invoke.WrongMethodTypeException;
  29 import java.net.URL;
  30 import java.net.URLClassLoader;
  31 import java.util.HashMap;
  32 import java.util.Map;
  33 import java.util.Random;
  34 import sun.invoke.util.Wrapper;
  35 import com.oracle.testlibrary.jsr292.Helper;
  36 
  37 /*
  38  * @test
  39  * @bug 8060483 8066746
  40  * @key randomness
  41  * @library /lib/testlibrary /lib/testlibrary/jsr292
  42  * @summary unit tests for MethodHandles.explicitCastArguments()
  43  * @run main ExplicitCastArgumentsTest
  44  */
  45 
  46 /**
  47  * Tests for MethodHandles.explicitCastArguments().
  48  */
  49 public class ExplicitCastArgumentsTest {
  50 
  51     private static final boolean VERBOSE = Helper.IS_VERBOSE;
  52     private static final Class<?> THIS_CLASS = ExplicitCastArgumentsTest.class;
  53     private static final Random RNG = Helper.RNG;
  54     private static final Map<Wrapper, Object> RANDOM_VALUES = new HashMap<>(9);
  55 
  56     static {
  57         RANDOM_VALUES.put(Wrapper.BOOLEAN, RNG.nextBoolean());
  58         RANDOM_VALUES.put(Wrapper.BYTE, (byte) RNG.nextInt());
  59         RANDOM_VALUES.put(Wrapper.SHORT, (short) RNG.nextInt());
  60         RANDOM_VALUES.put(Wrapper.CHAR, (char) RNG.nextInt());
  61         RANDOM_VALUES.put(Wrapper.INT, RNG.nextInt());
  62         RANDOM_VALUES.put(Wrapper.LONG, RNG.nextLong());
  63         RANDOM_VALUES.put(Wrapper.FLOAT, RNG.nextFloat());
  64         RANDOM_VALUES.put(Wrapper.DOUBLE, RNG.nextDouble());
  65         RANDOM_VALUES.put(Wrapper.OBJECT, new Object());
  66     }
  67 
  68     public static void main(String[] args) throws Throwable {
  69         testVarargsCollector();
  70         testNullRef2Prim();
  71         testRef2Prim();
  72         testPrim2Ref();
  73         testPrim2Prim();
  74         testNonBCPRef2Ref();
  75         testBCPRef2Ref();
  76         testReturnAny2Void();
  77         testReturnVoid2Any();
  78         testMultipleArgs();
  79         System.out.println("TEST PASSED");
  80     }
  81 
  82     /**
  83      * Dummy method used in {@link #testVarargsCollector} test to form a method
  84      * handle.
  85      *
  86      * @param args - any args
  87      * @return - returns args
  88      */
  89     public static String[] f(String... args) {
  90         return args;
  91     }
  92 
  93     /**
  94      * Tests that MHs.explicitCastArguments does incorrect type checks for
  95      * VarargsCollector. Bug 8066746.
  96      *
  97      * @throws java.lang.Throwable
  98      */
  99     public static void testVarargsCollector() throws Throwable {
 100         MethodType mt = MethodType.methodType(String[].class, String[].class);
 101         MethodHandle mh = MethodHandles.publicLookup()
 102                 .findStatic(THIS_CLASS, "f", mt);
 103         mh = MethodHandles.explicitCastArguments(mh,
 104                 MethodType.methodType(Object.class, Object.class));
 105         mh.invokeWithArguments((Object) (new String[]{"str1", "str2"}));
 106     }
 107 
 108     /**
 109      * Tests that null wrapper reference is successfully converted to primitive
 110      * types. Converted result should be zero for a primitive. Bug 8060483.
 111      */
 112     public static void testNullRef2Prim() {
 113         for (Wrapper from : Wrapper.values()) {
 114             for (Wrapper to : Wrapper.values()) {
 115                 if (from == Wrapper.VOID || to == Wrapper.VOID) {
 116                     continue;
 117                 }
 118                 // MHs.eCA javadoc:
 119                 //    If T0 is a reference and T1 a primitive, and if the reference
 120                 //    is null at runtime, a zero value is introduced.
 121                 for (TestConversionMode mode : TestConversionMode.values()) {
 122                     testConversion(mode, from.wrapperType(),
 123                             to.primitiveType(), null, to.zero(), false, null);
 124                 }
 125             }
 126         }
 127     }
 128 
 129     /**
 130      * Tests that non-null wrapper reference is successfully converted to
 131      * primitive types.
 132      */
 133     public static void testRef2Prim() {
 134         for (Wrapper from : Wrapper.values()) {
 135             for (Wrapper to : Wrapper.values()) {
 136                 if (from == Wrapper.VOID || to == Wrapper.VOID
 137                         || to == Wrapper.OBJECT) {
 138                     continue;
 139                 }
 140                 Object value = RANDOM_VALUES.get(from);
 141                 for (TestConversionMode mode : TestConversionMode.values()) {
 142                     if (from != Wrapper.OBJECT) {
 143                         Object convValue = to.wrap(value);
 144                         testConversion(mode, from.wrapperType(),
 145                                 to.primitiveType(), value, convValue, false, null);
 146                     } else {
 147                         testConversion(mode, from.wrapperType(),
 148                                 to.primitiveType(), value, null,
 149                                 true, ClassCastException.class);
 150                     }
 151                 }
 152             }
 153         }
 154     }
 155 
 156     /**
 157      * Tests that primitive is successfully converted to wrapper reference
 158      * types, to the Number type (if possible) and to the Object type.
 159      */
 160     public static void testPrim2Ref() {
 161         for (Wrapper from : Wrapper.values()) {
 162             for (Wrapper to : Wrapper.values()) {
 163                 if (from == Wrapper.VOID || from == Wrapper.OBJECT
 164                         || to == Wrapper.VOID || to == Wrapper.OBJECT) {
 165                     continue;
 166                 }
 167                 Object value = RANDOM_VALUES.get(from);
 168                 for (TestConversionMode mode : TestConversionMode.values()) {
 169                     if (from == to) {
 170                         testConversion(mode, from.primitiveType(),
 171                                 to.wrapperType(), value, value, false, null);
 172                     } else {
 173                         testConversion(mode, from.primitiveType(),
 174                                 to.wrapperType(), value, null, true, ClassCastException.class);
 175                     }
 176                     if (from != Wrapper.BOOLEAN && from != Wrapper.CHAR) {
 177                         testConversion(mode, from.primitiveType(),
 178                                 Number.class, value, value, false, null);
 179                     } else {
 180                         testConversion(mode, from.primitiveType(),
 181                                 Number.class, value, null,
 182                                 true, ClassCastException.class);
 183                     }
 184                     testConversion(mode, from.primitiveType(),
 185                             Object.class, value, value, false, null);
 186                 }
 187             }
 188         }
 189     }
 190 
 191     /**
 192      * Tests that primitive is successfully converted to other primitive type.
 193      */
 194     public static void testPrim2Prim() {
 195         for (Wrapper from : Wrapper.values()) {
 196             for (Wrapper to : Wrapper.values()) {
 197                 if (from == Wrapper.VOID || to == Wrapper.VOID
 198                         || from == Wrapper.OBJECT || to == Wrapper.OBJECT) {
 199                     continue;
 200                 }
 201                 Object value = RANDOM_VALUES.get(from);
 202                 Object convValue = to.wrap(value);
 203                 for (TestConversionMode mode : TestConversionMode.values()) {
 204                     testConversion(mode, from.primitiveType(),
 205                             to.primitiveType(), value, convValue, false, null);
 206                 }
 207             }
 208         }
 209     }
 210 
 211     /**
 212      * Dummy interface for {@link #testNonBCPRef2Ref} test.
 213      */
 214     public static interface TestInterface {}
 215 
 216     /**
 217      * Dummy class for {@link #testNonBCPRef2Ref} test.
 218      */
 219     public static class TestSuperClass implements TestInterface {}
 220 
 221     /**
 222      * Dummy class for {@link #testNonBCPRef2Ref} test.
 223      */
 224     public static class TestSubClass1 extends TestSuperClass {}
 225 
 226     /**
 227      * Dummy class for {@link #testNonBCPRef2Ref} test.
 228      */
 229     public static class TestSubClass2 extends TestSuperClass {}
 230 
 231     /**
 232      * Tests non-bootclasspath reference to reference conversions.
 233      * 
 234      * @throws java.lang.Throwable
 235      */
 236     public static void testNonBCPRef2Ref() throws Throwable {
 237         String testClassPath = System.getProperty("test.classes",".");
 238         URL[] classpath = {(new File(testClassPath)).getCanonicalFile()
 239             .toURI().toURL()};
 240         URLClassLoader ucl = URLClassLoader.newInstance(classpath);
 241         Class testInterface = ucl.loadClass(THIS_CLASS.getSimpleName()
 242                 + "$TestInterface");
 243         Class testSuperClass = ucl.loadClass(THIS_CLASS.getSimpleName()
 244                 + "$TestSuperClass");
 245         Class testSubClass1 = ucl.loadClass(THIS_CLASS.getSimpleName()
 246                 + "$TestSubClass1");
 247         Class testSubClass2 = TestSubClass2.class;
 248         Object testSuperObj = testSuperClass.newInstance();
 249         Object testObj01 = testSubClass1.newInstance();
 250         Object testObj02 = new TestSubClass2();
 251         Class[] parents = {testInterface, testSuperClass};
 252         Class[] children = {testSubClass1, testSubClass2};
 253         Object[] childInst = {testObj01, testObj02};
 254         for (TestConversionMode mode : TestConversionMode.values()) {
 255             for (int i = 0; i < parents.length; i++) {
 256                 for (int j = 0; j < children.length; j++) {
 257                     // Child type to parent type non-null conversion, shoud succeed
 258                     testConversion(mode, children[i], parents[j], childInst[i],
 259                             childInst[i], false, null);
 260                     // Child type to parent type null conversion, shoud succeed
 261                     testConversion(mode, children[i], parents[j], null,
 262                             null, false, null);
 263                     // Parent type to child type non-null conversion with parent
 264                     // type instance, should fail
 265                     testConversion(mode, parents[i],
 266                             children[j], testSuperObj, null, true,
 267                             ClassCastException.class);
 268                     // Parent type to child type non-null conversion with child
 269                     // type instance, should succeed
 270                     testConversion(mode, parents[i],
 271                             children[j], childInst[j], childInst[j], false, null);
 272                     // Parent type to child type null conversion, should succeed
 273                     testConversion(mode, parents[i],
 274                             children[j], null, null, false, null);
 275                 }
 276                 // Parent type to child type non-null conversion with sibling
 277                 // type instance, should fail
 278                 testConversion(mode, parents[i], testSubClass1, testObj02,
 279                         null, true, ClassCastException.class);
 280             }
 281             // Sibling type non-null conversion, should fail
 282             testConversion(mode, testSubClass1,
 283                     testSubClass2, testObj01, null, true,
 284                     ClassCastException.class);
 285             // Sibling type null conversion, should succeed
 286             testConversion(mode, testSubClass1,
 287                     testSubClass2, null, null, false, null);
 288         }
 289     }
 290 
 291     /**
 292      * Tests bootclasspath reference to reference conversions.
 293      */
 294     public static void testBCPRef2Ref() {
 295         Class bcpInterface = CharSequence.class;
 296         Class bcpSubClass1 = String.class;
 297         Class bcpSubClass2 = StringBuffer.class;
 298         Object testObj01 = new String("test");
 299         Object testObj02 = new StringBuffer("test");
 300         Class[] children = {bcpSubClass1, bcpSubClass2};
 301         Object[] childInst = {testObj01, testObj02};
 302         for (TestConversionMode mode : TestConversionMode.values()) {
 303             for (int i = 0; i < children.length; i++) {
 304                 // Child type to parent type non-null conversion, shoud succeed
 305                 testConversion(mode, children[i], bcpInterface, childInst[i],
 306                         childInst[i], false, null);
 307                 // Child type to parent type null conversion, shoud succeed
 308                 testConversion(mode, children[i], bcpInterface, null,
 309                         null, false, null);
 310                 // Parent type to child type non-null conversion with child
 311                 // type instance, should succeed
 312                 testConversion(mode, bcpInterface,
 313                         children[i], childInst[i], childInst[i], false, null);
 314                 // Parent type to child type null conversion, should succeed
 315                 testConversion(mode, bcpInterface,
 316                         children[i], null, null, false, null);
 317             }
 318             // Sibling type non-null conversion, should fail
 319             testConversion(mode, bcpSubClass1,
 320                     bcpSubClass2, testObj01, null, true,
 321                     ClassCastException.class);
 322             // Sibling type null conversion, should succeed
 323             testConversion(mode, bcpSubClass1,
 324                     bcpSubClass2, null, null, false, null);
 325             // Parent type to child type non-null conversion with sibling
 326             // type instance, should fail
 327             testConversion(mode, bcpInterface, bcpSubClass1, testObj02,
 328                     null, true, ClassCastException.class);
 329         }
 330     }
 331 
 332     /**
 333      * Dummy method used in {@link #testReturnAny2Void} and
 334      * {@link #testReturnVoid2Any} tests to form a method handle.
 335      */
 336     public static void retVoid() {}
 337 
 338     /**
 339      * Tests that non-null any return is successfully converted to non-type
 340      * void.
 341      */
 342     public static void testReturnAny2Void() {
 343         for (Wrapper from : Wrapper.values()) {
 344             testConversion(TestConversionMode.RETURN_VALUE, from.wrapperType(),
 345                     void.class, RANDOM_VALUES.get(from),
 346                     null, false, null);
 347             testConversion(TestConversionMode.RETURN_VALUE, from.primitiveType(),
 348                     void.class, RANDOM_VALUES.get(from),
 349                     null, false, null);
 350         }
 351     }
 352 
 353     /**
 354      * Tests that void return is successfully converted to primitive and
 355      * reference. Result should be zero for primitives and null for references.
 356      */
 357     public static void testReturnVoid2Any() {
 358         for (Wrapper to : Wrapper.values()) {
 359             testConversion(TestConversionMode.RETURN_VALUE, void.class,
 360                     to.primitiveType(), null,
 361                     to.zero(), false, null);
 362             testConversion(TestConversionMode.RETURN_VALUE, void.class,
 363                     to.wrapperType(), null,
 364                     null, false, null);
 365         }
 366     }
 367 
 368     private static void checkForWrongMethodTypeException(MethodHandle mh, MethodType mt) {
 369         try {
 370             MethodHandles.explicitCastArguments(mh, mt);
 371             throw new AssertionError("Expected WrongMethodTypeException is not thrown");
 372         } catch (WrongMethodTypeException wmte) {
 373             if (VERBOSE) {
 374                 System.out.printf("Expected exception %s: %s\n",
 375                         wmte.getClass(), wmte.getMessage());
 376             }
 377         }
 378     }
 379     
 380     /**
 381      * Tests that MHs.eCA method works correctly with MHs with multiple arguments.
 382      * @throws Throwable 
 383      */
 384     public static void testMultipleArgs() throws Throwable {
 385         int arity = 1 + RNG.nextInt(Helper.MAX_ARITY / 2 - 2);
 386         int arityMinus = RNG.nextInt(arity);
 387         int arityPlus = arity + RNG.nextInt(Helper.MAX_ARITY / 2 - arity) + 1;
 388         MethodType mType = Helper.randomMethodTypeGenerator(arity);
 389         MethodType mTypeNew = Helper.randomMethodTypeGenerator(arity);
 390         MethodType mTypeNewMinus = Helper.randomMethodTypeGenerator(arityMinus);
 391         MethodType mTypeNewPlus = Helper.randomMethodTypeGenerator(arityPlus);
 392         Class<?> rType = mType.returnType();
 393         MethodHandle original;
 394         if (rType.equals(void.class)) {
 395             MethodType mt = MethodType.methodType(void.class);
 396             original = MethodHandles.publicLookup()
 397                     .findStatic(THIS_CLASS, "retVoid", mt);
 398         } else {
 399             Object rValue = Helper.castToWrapper(1, rType);
 400             original = MethodHandles.constant(rType, rValue);
 401         }
 402         original = Helper.addTrailingArgs(original, arity, mType.parameterList());
 403         MethodHandle target = MethodHandles
 404                     .explicitCastArguments(original, mTypeNew);
 405         Object[] parList = Helper.randomArgs(mTypeNew.parameterList());
 406         for (int i = 0; i < parList.length; i++) {
 407             if (parList[i] instanceof String) {
 408                 parList[i] = null; //getting rid of Stings produced by randomArgs
 409             }
 410         }
 411         target.invokeWithArguments(parList);
 412         checkForWrongMethodTypeException(original, mTypeNewMinus);
 413         checkForWrongMethodTypeException(original, mTypeNewPlus);
 414     }
 415 
 416     /**
 417      * Enumeration of test conversion modes.
 418      */
 419     public enum TestConversionMode {
 420         RETURN_VALUE,
 421         ARGUMENT;
 422     }
 423 
 424     /**
 425      * Tests type and value conversion. Comparing with the given expected result.
 426      *
 427      * @param mode - test conversion mode. See {@link #TestConversionMode}.
 428      * @param from - source type.
 429      * @param to - destination type.
 430      * @param param - value to be converted.
 431      * @param expectedResult - expected value after conversion.
 432      * @param failureExpected - true if conversion failure expected.
 433      * @param expectedException - expected exception class if
 434      * {@code failureExpected} is true.
 435      */
 436     public static void testConversion(TestConversionMode mode,
 437             Class<?> from, Class<?> to, Object param,
 438             Object expectedResult, boolean failureExpected,
 439             Class<? extends Throwable> expectedException) {
 440         if (VERBOSE) {
 441             System.out.printf("Testing return value conversion: "
 442                     + "%-10s => %-10s: %5s: ", from.getSimpleName(),
 443                     to.getSimpleName(), param);
 444         }
 445         MethodHandle original = null;
 446         MethodType newType = null;
 447         switch (mode) {
 448             case RETURN_VALUE:
 449                 if (from.equals(void.class)) {
 450                     MethodType mt = MethodType.methodType(void.class);
 451                     try {
 452                         original = MethodHandles.publicLookup()
 453                                 .findStatic(THIS_CLASS, "retVoid", mt);
 454                     } catch (NoSuchMethodException | IllegalAccessException ex) {
 455                         throw new Error("Unexpected issue", ex);
 456                     }
 457                 } else {
 458                     original = MethodHandles.constant(from, param);
 459                 }
 460                 newType = original.type().changeReturnType(to);
 461                 break;
 462             case ARGUMENT:
 463                 if (from.equals(void.class) || to.equals(void.class)) {
 464                     throw new Error("Test issue: argument conversion does not"
 465                             + " work with non-type void");
 466                 }
 467                 original = MethodHandles.identity(to);
 468                 newType = original.type().changeParameterType(0, from);
 469                 break;
 470             default:
 471                 String msg = String.format("Test issue: unknown test"
 472                         + " convertion mode %s.", mode.name());
 473                 throw new Error(msg);
 474         }
 475         try {
 476             MethodHandle target = MethodHandles
 477                     .explicitCastArguments(original, newType);
 478             Object result;
 479             switch (mode) {
 480                 case RETURN_VALUE:
 481                     result = target.invokeWithArguments();
 482                     break;
 483                 case ARGUMENT:
 484                     result = target.invokeWithArguments(param);
 485                     break;
 486                 default:
 487                     String msg = String.format("Test issue: unknown test"
 488                             + " convertion mode %s.", mode.name());
 489                     throw new Error(msg);
 490             }
 491             if (!failureExpected
 492                     && (expectedResult != null && !expectedResult.equals(result)
 493                     || expectedResult == null && result != null)) {
 494                 String msg = String.format("Conversion result %s is not equal"
 495                         + " to the expected result %10s",
 496                         result, expectedResult);
 497                 throw new AssertionError(msg);
 498             }
 499             if (VERBOSE) {
 500                 String resultStr;
 501                 if (result != null) {
 502                     resultStr = String.format("Converted value and type are"
 503                             + " %10s (%10s)", "'" + result + "'",
 504                             result.getClass().getSimpleName());
 505                 } else {
 506                     resultStr = String.format("Converted value is %10s", result);
 507                 }
 508                 System.out.println(resultStr);
 509             }
 510             if (failureExpected) {
 511                 String msg = String.format("No exception thrown while testing"
 512                         + " return value conversion: %10s => %10s;"
 513                         + " parameter: %10s",
 514                         from, to, param);
 515                 throw new AssertionError(msg);
 516             }
 517         } catch (AssertionError e) {
 518             throw e; // report test failure
 519         } catch (Throwable e) {
 520             if (VERBOSE) {
 521                 System.out.printf("%s: %s\n", e.getClass(), e.getMessage());
 522             }
 523             if (!failureExpected || !e.getClass().equals(expectedException)) {
 524                 String msg = String.format("Unexpected exception was thrown"
 525                         + " while testing return value conversion:"
 526                         + " %s => %s; parameter: %s", from, to, param);
 527                 throw new AssertionError(msg, e);
 528             }
 529         }
 530     }
 531 }