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