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