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