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