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