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