jdk/test/java/lang/invoke/MethodHandles/CatchExceptionTest.java
changeset 23594 505c3a4eb0d6
child 26599 e82af948593b
equal deleted inserted replaced
23593:318e66c8de0e 23594:505c3a4eb0d6
       
     1 /*
       
     2  * Copyright (c) 2014, 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 package test.java.lang.invoke.MethodHandles;
       
    24 
       
    25 import com.oracle.testlibrary.jsr292.Helper;
       
    26 import jdk.testlibrary.Asserts;
       
    27 
       
    28 import java.lang.invoke.MethodHandle;
       
    29 import java.lang.invoke.MethodHandles;
       
    30 import java.lang.invoke.MethodType;
       
    31 import java.lang.reflect.Array;
       
    32 import java.util.*;
       
    33 import java.util.function.BiFunction;
       
    34 import java.util.function.Function;
       
    35 import java.util.function.Supplier;
       
    36 
       
    37 /* @test
       
    38  * @library /lib/testlibrary/jsr292 /lib/testlibrary/
       
    39  * @compile CatchExceptionTest.java
       
    40  * @run main/othervm -esa test.java.lang.invoke.MethodHandles.CatchExceptionTest
       
    41  */
       
    42 public class CatchExceptionTest {
       
    43     private static final List<Class<?>> ARGS_CLASSES;
       
    44     protected static final int MAX_ARITY = Helper.MAX_ARITY - 1;
       
    45     static {
       
    46         Class<?> classes[] = {
       
    47                 Object.class,
       
    48                 long.class,
       
    49                 int.class,
       
    50                 byte.class,
       
    51                 Integer[].class,
       
    52                 double[].class,
       
    53                 String.class,
       
    54         };
       
    55         List<Class<?>> list = new ArrayList<>(MAX_ARITY);
       
    56         for (int i = 0; i < MAX_ARITY; ++i) {
       
    57             list.add(classes[Helper.RNG.nextInt(classes.length)]);
       
    58         }
       
    59         ARGS_CLASSES = Collections.unmodifiableList(list);
       
    60     }
       
    61 
       
    62     private final TestCase testCase;
       
    63     private final int nargs;
       
    64     private final int argsCount;
       
    65     private final MethodHandle catcher;
       
    66     private int dropped;
       
    67     private MethodHandle thrower;
       
    68 
       
    69 
       
    70     public CatchExceptionTest(TestCase testCase, final boolean isVararg, final int argsCount,
       
    71             final int catchDrops) {
       
    72         this.testCase = testCase;
       
    73         this.dropped = catchDrops;
       
    74         if (Helper.IS_VERBOSE) {
       
    75             System.out.printf("CatchException::CatchException(%s, isVararg=%b " +
       
    76                             "argsCount=%d catchDrops=%d)%n",
       
    77                     testCase, isVararg, argsCount, catchDrops
       
    78             );
       
    79         }
       
    80         MethodHandle thrower = testCase.thrower;
       
    81         int throwerLen = thrower.type().parameterCount();
       
    82         List<Class<?>> classes;
       
    83         int extra = Math.max(0, argsCount - throwerLen);
       
    84         classes = getThrowerParams(isVararg, extra);
       
    85         this.argsCount = throwerLen + classes.size();
       
    86         thrower = Helper.addTrailingArgs(thrower, this.argsCount, classes);
       
    87         if (isVararg && argsCount > throwerLen) {
       
    88             MethodType mt = thrower.type();
       
    89             Class<?> lastParam = mt.parameterType(mt.parameterCount() - 1);
       
    90             thrower = thrower.asVarargsCollector(lastParam);
       
    91         }
       
    92         this.thrower = thrower;
       
    93         this.dropped = Math.min(this.argsCount, catchDrops);
       
    94         catcher = testCase.getCatcher(getCatcherParams());
       
    95         nargs = Math.max(2, this.argsCount);
       
    96     }
       
    97 
       
    98     public static void main(String[] args) throws Throwable {
       
    99         for (CatchExceptionTest test : TestFactory.MANDATORY_TEST_CASES) {
       
   100             test.runTest();
       
   101         }
       
   102         TestFactory factory = new TestFactory();
       
   103         CatchExceptionTest test;
       
   104         while ((test = factory.nextTest()) != null ) {
       
   105             test.runTest();
       
   106         }
       
   107     }
       
   108 
       
   109     private List<Class<?>> getThrowerParams(boolean isVararg, int argsCount) {
       
   110         boolean unmodifiable = true;
       
   111         List<Class<?>> classes;
       
   112         classes = ARGS_CLASSES.subList(0,
       
   113                 Math.min(argsCount, (MAX_ARITY / 2) - 1));
       
   114         int extra = 0;
       
   115         if (argsCount >= MAX_ARITY / 2) {
       
   116             classes = new ArrayList<>(classes);
       
   117             unmodifiable = false;
       
   118             extra = (int) classes.stream().filter(Helper::isDoubleCost).count();
       
   119             int i = classes.size();
       
   120             while (classes.size() + extra < argsCount) {
       
   121                 Class<?> aClass = ARGS_CLASSES.get(i);
       
   122                 if (Helper.isDoubleCost(aClass)) {
       
   123                     ++extra;
       
   124                     if (classes.size() + extra >= argsCount) {
       
   125                         break;
       
   126                     }
       
   127                 }
       
   128                 classes.add(aClass);
       
   129             }
       
   130         }
       
   131         if (isVararg && classes.size() > 0) {
       
   132             if (unmodifiable) {
       
   133                 classes = new ArrayList<>(classes);
       
   134             }
       
   135             int last = classes.size() - 1;
       
   136             Class<?> aClass = classes.get(classes.size() - 1);
       
   137             aClass = Array.newInstance(aClass, 2).getClass();
       
   138             classes.set(last, aClass);
       
   139         }
       
   140         return classes;
       
   141     }
       
   142 
       
   143 
       
   144     private List<Class<?>> getCatcherParams() {
       
   145         int catchArgc = 1 + this.argsCount - dropped;
       
   146         List<Class<?>> result = new ArrayList<>(
       
   147                 thrower.type().parameterList().subList(0, catchArgc - 1));
       
   148         // prepend throwable
       
   149         result.add(0, testCase.throwableClass);
       
   150         return result;
       
   151     }
       
   152 
       
   153     private void runTest() {
       
   154         Helper.clear();
       
   155 
       
   156         Object[] args = Helper.randomArgs(
       
   157                 argsCount, thrower.type().parameterArray());
       
   158         Object arg0 = Helper.MISSING_ARG;
       
   159         Object arg1 = testCase.thrown;
       
   160         if (argsCount > 0) {
       
   161             arg0 = args[0];
       
   162         }
       
   163         if (argsCount > 1) {
       
   164             args[1] = arg1;
       
   165         }
       
   166         Asserts.assertEQ(nargs, thrower.type().parameterCount());
       
   167         if (argsCount < nargs) {
       
   168             Object[] appendArgs = {arg0, arg1};
       
   169             appendArgs = Arrays.copyOfRange(appendArgs, argsCount, nargs);
       
   170             thrower = MethodHandles.insertArguments(
       
   171                     thrower, argsCount, appendArgs);
       
   172         }
       
   173         Asserts.assertEQ(argsCount, thrower.type().parameterCount());
       
   174 
       
   175         MethodHandle target = MethodHandles.catchException(
       
   176                 testCase.filter(thrower), testCase.throwableClass,
       
   177                 testCase.filter(catcher));
       
   178 
       
   179         Asserts.assertEQ(thrower.type(), target.type());
       
   180         Asserts.assertEQ(argsCount, target.type().parameterCount());
       
   181 
       
   182         Object returned;
       
   183         try {
       
   184             returned = target.invokeWithArguments(args);
       
   185         } catch (Throwable ex) {
       
   186             testCase.assertCatch(ex);
       
   187             returned = ex;
       
   188         }
       
   189 
       
   190         testCase.assertReturn(returned, arg0, arg1, dropped, args);
       
   191     }
       
   192 }
       
   193 
       
   194 class TestFactory {
       
   195     public static final List<CatchExceptionTest> MANDATORY_TEST_CASES = new ArrayList<>();
       
   196 
       
   197     private static final int MIN_TESTED_ARITY = 10;
       
   198 
       
   199     static {
       
   200         for (int[] args : new int[][]{
       
   201                 {0, 0},
       
   202                 {MIN_TESTED_ARITY, 0},
       
   203                 {MIN_TESTED_ARITY, MIN_TESTED_ARITY},
       
   204                 {CatchExceptionTest.MAX_ARITY, 0},
       
   205                 {CatchExceptionTest.MAX_ARITY, CatchExceptionTest.MAX_ARITY},
       
   206         }) {
       
   207                 MANDATORY_TEST_CASES.addAll(createTests(args[0], args[1]));
       
   208         }
       
   209     }
       
   210 
       
   211     private int count;
       
   212     private int args;
       
   213     private int dropArgs;
       
   214     private int currentMaxDrops;
       
   215     private int maxArgs;
       
   216     private int maxDrops;
       
   217     private int constructor;
       
   218     private int constructorSize;
       
   219     private boolean isVararg;
       
   220 
       
   221     public TestFactory() {
       
   222         if (Helper.IS_THOROUGH) {
       
   223             maxArgs = maxDrops = CatchExceptionTest.MAX_ARITY;
       
   224         } else {
       
   225             maxArgs = MIN_TESTED_ARITY
       
   226                     + Helper.RNG.nextInt(CatchExceptionTest.MAX_ARITY
       
   227                             - MIN_TESTED_ARITY)
       
   228                     + 1;
       
   229             maxDrops = MIN_TESTED_ARITY
       
   230                     + Helper.RNG.nextInt(maxArgs - MIN_TESTED_ARITY)
       
   231                     + 1;
       
   232             args = 1;
       
   233         }
       
   234 
       
   235         if (Helper.IS_VERBOSE) {
       
   236             System.out.printf("maxArgs = %d%nmaxDrops = %d%n",
       
   237                     maxArgs, maxDrops);
       
   238         }
       
   239         constructorSize = TestCase.CONSTRUCTORS.size();
       
   240     }
       
   241 
       
   242     private static List<CatchExceptionTest> createTests(int argsCount,
       
   243             int catchDrops) {
       
   244         if (catchDrops > argsCount || argsCount < 0 || catchDrops < 0) {
       
   245             throw new IllegalArgumentException("argsCount = " + argsCount
       
   246                     + ", catchDrops = " + catchDrops
       
   247             );
       
   248         }
       
   249         List<CatchExceptionTest> result = new ArrayList<>(
       
   250                 TestCase.CONSTRUCTORS.size());
       
   251         for (Supplier<TestCase> constructor : TestCase.CONSTRUCTORS) {
       
   252             result.add(new CatchExceptionTest(constructor.get(),
       
   253                     /* isVararg = */ true,
       
   254                     argsCount,
       
   255                     catchDrops));
       
   256             result.add(new CatchExceptionTest(constructor.get(),
       
   257                     /* isVararg = */ false,
       
   258                     argsCount,
       
   259                     catchDrops));
       
   260         }
       
   261         return result;
       
   262     }
       
   263 
       
   264     /**
       
   265      * @return next test from test matrix:
       
   266      * {varArgs, noVarArgs} x TestCase.rtypes x TestCase.THROWABLES x {1, .., maxArgs } x {1, .., maxDrops}
       
   267      */
       
   268     public CatchExceptionTest nextTest() {
       
   269         if (constructor < constructorSize) {
       
   270             return createTest();
       
   271         }
       
   272         constructor = 0;
       
   273         count++;
       
   274         if (!Helper.IS_THOROUGH && count > Helper.TEST_LIMIT) {
       
   275             System.out.println("test limit is exceeded");
       
   276             return null;
       
   277         }
       
   278         if (dropArgs <= currentMaxDrops) {
       
   279             if (dropArgs == 1) {
       
   280                 if (Helper.IS_THOROUGH || Helper.RNG.nextBoolean()) {
       
   281                     ++dropArgs;
       
   282                     return createTest();
       
   283                 } else if (Helper.IS_VERBOSE) {
       
   284                     System.out.printf(
       
   285                             "argsCount=%d : \"drop\" scenarios are skipped%n",
       
   286                             args);
       
   287                 }
       
   288             } else {
       
   289                 ++dropArgs;
       
   290                 return createTest();
       
   291             }
       
   292         }
       
   293 
       
   294         if (args <= maxArgs) {
       
   295             dropArgs = 1;
       
   296             currentMaxDrops = Math.min(args, maxDrops);
       
   297             ++args;
       
   298             return createTest();
       
   299         }
       
   300         return null;
       
   301     }
       
   302 
       
   303     private CatchExceptionTest createTest() {
       
   304         if (!Helper.IS_THOROUGH) {
       
   305             return new CatchExceptionTest(
       
   306                     TestCase.CONSTRUCTORS.get(constructor++).get(),
       
   307                     Helper.RNG.nextBoolean(), args, dropArgs);
       
   308         } else {
       
   309            if (isVararg) {
       
   310                isVararg = false;
       
   311                return new CatchExceptionTest(
       
   312                        TestCase.CONSTRUCTORS.get(constructor++).get(),
       
   313                        isVararg, args, dropArgs);
       
   314            } else {
       
   315                isVararg = true;
       
   316                return new CatchExceptionTest(
       
   317                        TestCase.CONSTRUCTORS.get(constructor).get(),
       
   318                        isVararg, args, dropArgs);
       
   319            }
       
   320         }
       
   321     }
       
   322 }
       
   323 
       
   324 class TestCase<T> {
       
   325     private static enum ThrowMode {
       
   326         NOTHING,
       
   327         CAUGHT,
       
   328         UNCAUGHT,
       
   329         ADAPTER
       
   330     }
       
   331 
       
   332     @SuppressWarnings("unchecked")
       
   333     public static final List<Supplier<TestCase>> CONSTRUCTORS;
       
   334     private static final MethodHandle FAKE_IDENTITY;
       
   335     private static final MethodHandle THROW_OR_RETURN;
       
   336     private static final MethodHandle CATCHER;
       
   337 
       
   338     static {
       
   339         try {
       
   340             MethodHandles.Lookup lookup = MethodHandles.lookup();
       
   341             THROW_OR_RETURN = lookup.findStatic(
       
   342                     TestCase.class,
       
   343                     "throwOrReturn",
       
   344                     MethodType.methodType(Object.class, Object.class,
       
   345                             Throwable.class)
       
   346             );
       
   347             CATCHER = lookup.findStatic(
       
   348                     TestCase.class,
       
   349                     "catcher",
       
   350                     MethodType.methodType(Object.class, Object.class));
       
   351             FAKE_IDENTITY = lookup.findVirtual(
       
   352                     TestCase.class, "fakeIdentity",
       
   353                     MethodType.methodType(Object.class, Object.class));
       
   354 
       
   355         } catch (NoSuchMethodException | IllegalAccessException e) {
       
   356             throw new Error(e);
       
   357         }
       
   358         PartialConstructor[] constructors = {
       
   359                 create(Object.class, Object.class::cast),
       
   360                 create(String.class, Objects::toString),
       
   361                 create(int[].class, x -> new int[]{Objects.hashCode(x)}),
       
   362                 create(long.class,
       
   363                         x -> Objects.hashCode(x) & (-1L >>> 32)),
       
   364                 create(void.class, TestCase::noop)};
       
   365         Throwable[] throwables = {
       
   366                 new ClassCastException("testing"),
       
   367                 new java.io.IOException("testing"),
       
   368                 new LinkageError("testing")};
       
   369         List<Supplier<TestCase>> list = new ArrayList<>(constructors.length *
       
   370                 throwables.length * ThrowMode.values().length);
       
   371         //noinspection unchecked
       
   372         for (PartialConstructor f : constructors) {
       
   373             for (ThrowMode mode : ThrowMode.values()) {
       
   374                 for (Throwable t : throwables) {
       
   375                     list.add(f.apply(mode, t));
       
   376                 }
       
   377             }
       
   378         }
       
   379         CONSTRUCTORS = Collections.unmodifiableList(list);
       
   380     }
       
   381 
       
   382     public final Class<T> rtype;
       
   383     public final ThrowMode throwMode;
       
   384     public final Throwable thrown;
       
   385     public final Class<? extends Throwable> throwableClass;
       
   386     /**
       
   387      * MH which takes 2 args (Object,Throwable), 1st is the return value,
       
   388      * 2nd is the exception which will be thrown, if it's supposed in current
       
   389      * {@link #throwMode}.
       
   390      */
       
   391     public final MethodHandle thrower;
       
   392     private final Function<Object, T> cast;
       
   393     protected MethodHandle filter;
       
   394     private int fakeIdentityCount;
       
   395 
       
   396     private TestCase(Class<T> rtype, Function<Object, T> cast,
       
   397             ThrowMode throwMode, Throwable thrown)
       
   398             throws NoSuchMethodException, IllegalAccessException {
       
   399         this.cast = cast;
       
   400         filter = MethodHandles.lookup().findVirtual(
       
   401                 Function.class,
       
   402                 "apply",
       
   403                 MethodType.methodType(Object.class, Object.class))
       
   404                               .bindTo(cast);
       
   405         this.rtype = rtype;
       
   406         this.throwMode = throwMode;
       
   407         this.throwableClass = thrown.getClass();
       
   408         switch (throwMode) {
       
   409             case NOTHING:
       
   410                 this.thrown = null;
       
   411                 break;
       
   412             case ADAPTER:
       
   413             case UNCAUGHT:
       
   414                 this.thrown = new Error("do not catch this");
       
   415                 break;
       
   416             default:
       
   417                 this.thrown = thrown;
       
   418         }
       
   419 
       
   420         MethodHandle throwOrReturn = THROW_OR_RETURN;
       
   421         if (throwMode == ThrowMode.ADAPTER) {
       
   422             MethodHandle fakeIdentity = FAKE_IDENTITY.bindTo(this);
       
   423             for (int i = 0; i < 10; ++i) {
       
   424                 throwOrReturn = MethodHandles.filterReturnValue(
       
   425                         throwOrReturn, fakeIdentity);
       
   426             }
       
   427         }
       
   428         thrower = throwOrReturn.asType(MethodType.genericMethodType(2));
       
   429     }
       
   430 
       
   431     private static Void noop(Object x) {
       
   432         return null;
       
   433     }
       
   434 
       
   435     private static <T2> PartialConstructor create(
       
   436             Class<T2> rtype, Function<Object, T2> cast) {
       
   437         return (t, u) -> () -> {
       
   438             try {
       
   439                 return new TestCase<>(rtype, cast, t, u);
       
   440             } catch (NoSuchMethodException | IllegalAccessException e) {
       
   441                 throw new Error(e);
       
   442             }
       
   443         };
       
   444     }
       
   445 
       
   446     private static <T extends Throwable>
       
   447     Object throwOrReturn(Object normal, T exception) throws T {
       
   448         if (exception != null) {
       
   449             Helper.called("throwOrReturn/throw", normal, exception);
       
   450             throw exception;
       
   451         }
       
   452         Helper.called("throwOrReturn/normal", normal, exception);
       
   453         return normal;
       
   454     }
       
   455 
       
   456     private static <T extends Throwable>
       
   457     Object catcher(Object o) {
       
   458         Helper.called("catcher", o);
       
   459         return o;
       
   460     }
       
   461 
       
   462     public MethodHandle filter(MethodHandle target) {
       
   463         return MethodHandles.filterReturnValue(target, filter);
       
   464     }
       
   465 
       
   466     public MethodHandle getCatcher(List<Class<?>> classes) {
       
   467         return MethodHandles.filterReturnValue(Helper.AS_LIST.asType(
       
   468                         MethodType.methodType(Object.class, classes)),
       
   469                 CATCHER
       
   470         );
       
   471     }
       
   472 
       
   473     @Override
       
   474     public String toString() {
       
   475         return "TestCase{" +
       
   476                 "rtype=" + rtype +
       
   477                 ", throwMode=" + throwMode +
       
   478                 ", throwableClass=" + throwableClass +
       
   479                 '}';
       
   480     }
       
   481 
       
   482     public String callName() {
       
   483         return "throwOrReturn/" +
       
   484                 (throwMode == ThrowMode.NOTHING
       
   485                         ? "normal"
       
   486                         : "throw");
       
   487     }
       
   488 
       
   489     public void assertReturn(Object returned, Object arg0, Object arg1,
       
   490             int catchDrops, Object... args) {
       
   491         int lag = 0;
       
   492         if (throwMode == ThrowMode.CAUGHT) {
       
   493             lag = 1;
       
   494         }
       
   495         Helper.assertCalled(lag, callName(), arg0, arg1);
       
   496 
       
   497         if (throwMode == ThrowMode.NOTHING) {
       
   498             assertEQ(cast.apply(arg0), returned);
       
   499         } else if (throwMode == ThrowMode.CAUGHT) {
       
   500             List<Object> catchArgs = new ArrayList<>(Arrays.asList(args));
       
   501             // catcher receives an initial subsequence of target arguments:
       
   502             catchArgs.subList(args.length - catchDrops, args.length).clear();
       
   503             // catcher also receives the exception, prepended:
       
   504             catchArgs.add(0, thrown);
       
   505             Helper.assertCalled("catcher", catchArgs);
       
   506             assertEQ(cast.apply(catchArgs), returned);
       
   507         }
       
   508         Asserts.assertEQ(0, fakeIdentityCount);
       
   509     }
       
   510 
       
   511     private void assertEQ(T t, Object returned) {
       
   512         if (rtype.isArray()) {
       
   513             Asserts.assertEQ(t.getClass(), returned.getClass());
       
   514             int n = Array.getLength(t);
       
   515             Asserts.assertEQ(n, Array.getLength(returned));
       
   516             for (int i = 0; i < n; ++i) {
       
   517                 Asserts.assertEQ(Array.get(t, i), Array.get(returned, i));
       
   518             }
       
   519         } else {
       
   520             Asserts.assertEQ(t, returned);
       
   521         }
       
   522     }
       
   523 
       
   524     private Object fakeIdentity(Object x) {
       
   525         System.out.println("should throw through this!");
       
   526         ++fakeIdentityCount;
       
   527         return x;
       
   528     }
       
   529 
       
   530     public void assertCatch(Throwable ex) {
       
   531         try {
       
   532             Asserts.assertSame(thrown, ex,
       
   533                     "must get the out-of-band exception");
       
   534         } catch (Throwable t) {
       
   535             ex.printStackTrace();
       
   536         }
       
   537     }
       
   538 
       
   539     public interface PartialConstructor
       
   540             extends BiFunction<ThrowMode, Throwable, Supplier<TestCase>> {
       
   541     }
       
   542 }