| /* |
| * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package test.java.lang.invoke.MethodHandles; |
| |
| import jdk.test.lib.TimeLimitedRunner; |
| import jdk.testlibrary.Asserts; |
| import jdk.testlibrary.Utils; |
| import test.java.lang.invoke.lib.CodeCacheOverflowProcessor; |
| import test.java.lang.invoke.lib.Helper; |
| |
| import java.lang.invoke.MethodHandle; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.MethodType; |
| import java.lang.reflect.Array; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.function.BiFunction; |
| import java.util.function.Function; |
| import java.util.function.Supplier; |
| |
| /* @test |
| * @library /lib/testlibrary /java/lang/invoke/common /test/lib |
| * @build jdk.test.lib.TimeLimitedRunner |
| * @compile CatchExceptionTest.java |
| * @run main/othervm -esa test.java.lang.invoke.MethodHandles.CatchExceptionTest |
| * @key intermittent randomness |
| */ |
| public class CatchExceptionTest { |
| private static final List<Class<?>> ARGS_CLASSES; |
| protected static final int MAX_ARITY = Helper.MAX_ARITY - 1; |
| |
| static { |
| Class<?> classes[] = { |
| Object.class, |
| long.class, |
| int.class, |
| byte.class, |
| Integer[].class, |
| double[].class, |
| String.class, |
| }; |
| ARGS_CLASSES = Collections.unmodifiableList( |
| Helper.randomClasses(classes, MAX_ARITY)); |
| } |
| |
| private final TestCase testCase; |
| private final int nargs; |
| private final int argsCount; |
| private final MethodHandle catcher; |
| private int dropped; |
| private MethodHandle thrower; |
| |
| public CatchExceptionTest(TestCase testCase, final boolean isVararg, |
| final int argsCount, final int catchDrops) { |
| this.testCase = testCase; |
| this.dropped = catchDrops; |
| MethodHandle thrower = testCase.thrower; |
| int throwerLen = thrower.type().parameterCount(); |
| List<Class<?>> classes; |
| int extra = Math.max(0, argsCount - throwerLen); |
| classes = getThrowerParams(isVararg, extra); |
| this.argsCount = throwerLen + classes.size(); |
| thrower = Helper.addTrailingArgs(thrower, this.argsCount, classes); |
| if (isVararg && argsCount > throwerLen) { |
| MethodType mt = thrower.type(); |
| Class<?> lastParam = mt.parameterType(mt.parameterCount() - 1); |
| thrower = thrower.asVarargsCollector(lastParam); |
| } |
| this.thrower = thrower; |
| this.dropped = Math.min(this.argsCount, catchDrops); |
| catcher = testCase.getCatcher(getCatcherParams()); |
| nargs = Math.max(2, this.argsCount); |
| } |
| |
| public static void main(String[] args) throws Throwable { |
| CodeCacheOverflowProcessor.runMHTest(CatchExceptionTest::test); |
| } |
| |
| public static void test() throws Throwable { |
| System.out.println("classes = " + ARGS_CLASSES); |
| |
| TestFactory factory = new TestFactory(); |
| long timeout = Helper.IS_THOROUGH ? 0L : Utils.adjustTimeout(Utils.DEFAULT_TEST_TIMEOUT); |
| // subtract vm init time and reserve time for vm exit |
| timeout *= 0.9; |
| TimeLimitedRunner runner = new TimeLimitedRunner(timeout, 2.0d, |
| () -> { |
| CatchExceptionTest test = factory.nextTest(); |
| if (test != null) { |
| test.runTest(); |
| return true; |
| } |
| return false; |
| }); |
| for (CatchExceptionTest test : TestFactory.MANDATORY_TEST_CASES) { |
| test.runTest(); |
| } |
| runner.call(); |
| } |
| |
| private List<Class<?>> getThrowerParams(boolean isVararg, int argsCount) { |
| return Helper.getParams(ARGS_CLASSES, isVararg, argsCount); |
| } |
| |
| private List<Class<?>> getCatcherParams() { |
| int catchArgc = 1 + this.argsCount - dropped; |
| List<Class<?>> result = new ArrayList<>( |
| thrower.type().parameterList().subList(0, catchArgc - 1)); |
| // prepend throwable |
| result.add(0, testCase.throwableClass); |
| return result; |
| } |
| |
| private void runTest() { |
| if (Helper.IS_VERBOSE) { |
| System.out.printf("CatchException(%s, isVararg=%b argsCount=%d " + |
| "dropped=%d)%n", |
| testCase, thrower.isVarargsCollector(), argsCount, dropped); |
| } |
| |
| Helper.clear(); |
| |
| Object[] args = Helper.randomArgs( |
| argsCount, thrower.type().parameterArray()); |
| Object arg0 = Helper.MISSING_ARG; |
| Object arg1 = testCase.thrown; |
| if (argsCount > 0) { |
| arg0 = args[0]; |
| } |
| if (argsCount > 1) { |
| args[1] = arg1; |
| } |
| Asserts.assertEQ(nargs, thrower.type().parameterCount()); |
| if (argsCount < nargs) { |
| Object[] appendArgs = {arg0, arg1}; |
| appendArgs = Arrays.copyOfRange(appendArgs, argsCount, nargs); |
| thrower = MethodHandles.insertArguments( |
| thrower, argsCount, appendArgs); |
| } |
| Asserts.assertEQ(argsCount, thrower.type().parameterCount()); |
| |
| MethodHandle target = MethodHandles.catchException( |
| testCase.filter(thrower), testCase.throwableClass, |
| testCase.filter(catcher)); |
| |
| Asserts.assertEQ(thrower.type(), target.type()); |
| Asserts.assertEQ(argsCount, target.type().parameterCount()); |
| |
| Object returned; |
| try { |
| returned = target.invokeWithArguments(args); |
| } catch (Throwable ex) { |
| if (CodeCacheOverflowProcessor.isThrowableCausedByVME(ex)) { |
| // This error will be treated by CodeCacheOverflowProcessor |
| // to prevent the test from failing because of code cache overflow. |
| throw new Error(ex); |
| } |
| testCase.assertCatch(ex); |
| returned = ex; |
| } |
| |
| testCase.assertReturn(returned, arg0, arg1, dropped, args); |
| } |
| } |
| |
| class TestFactory { |
| public static final List<CatchExceptionTest> MANDATORY_TEST_CASES = new ArrayList<>(); |
| |
| private static final int MIN_TESTED_ARITY = 10; |
| |
| static { |
| for (int[] args : new int[][]{ |
| {0, 0}, |
| {MIN_TESTED_ARITY, 0}, |
| {MIN_TESTED_ARITY, MIN_TESTED_ARITY}, |
| {CatchExceptionTest.MAX_ARITY, 0}, |
| {CatchExceptionTest.MAX_ARITY, CatchExceptionTest.MAX_ARITY}, |
| }) { |
| MANDATORY_TEST_CASES.addAll(createTests(args[0], args[1])); |
| } |
| } |
| |
| private int count; |
| private int args; |
| private int dropArgs; |
| private int currentMaxDrops; |
| private int maxArgs; |
| private int maxDrops; |
| private int constructor; |
| private int constructorSize; |
| private boolean isVararg; |
| |
| public TestFactory() { |
| if (Helper.IS_THOROUGH) { |
| maxArgs = maxDrops = CatchExceptionTest.MAX_ARITY; |
| } else { |
| maxArgs = MIN_TESTED_ARITY |
| + Helper.RNG.nextInt(CatchExceptionTest.MAX_ARITY |
| - MIN_TESTED_ARITY) |
| + 1; |
| maxDrops = MIN_TESTED_ARITY |
| + Helper.RNG.nextInt(maxArgs - MIN_TESTED_ARITY) |
| + 1; |
| args = 1; |
| } |
| |
| System.out.printf("maxArgs = %d%nmaxDrops = %d%n", maxArgs, maxDrops); |
| constructorSize = TestCase.CONSTRUCTORS.size(); |
| } |
| |
| private static List<CatchExceptionTest> createTests(int argsCount, |
| int catchDrops) { |
| if (catchDrops > argsCount || argsCount < 0 || catchDrops < 0) { |
| throw new IllegalArgumentException("argsCount = " + argsCount |
| + ", catchDrops = " + catchDrops |
| ); |
| } |
| List<CatchExceptionTest> result = new ArrayList<>( |
| TestCase.CONSTRUCTORS.size()); |
| for (Supplier<TestCase> constructor : TestCase.CONSTRUCTORS) { |
| result.add(new CatchExceptionTest(constructor.get(), |
| /* isVararg = */ true, |
| argsCount, |
| catchDrops)); |
| result.add(new CatchExceptionTest(constructor.get(), |
| /* isVararg = */ false, |
| argsCount, |
| catchDrops)); |
| } |
| return result; |
| } |
| |
| /** |
| * @return next test from test matrix: |
| * {varArgs, noVarArgs} x TestCase.rtypes x TestCase.THROWABLES x {1, .., maxArgs } x {0, .., maxDrops} |
| */ |
| public CatchExceptionTest nextTest() { |
| if (constructor < constructorSize) { |
| return createTest(); |
| } |
| constructor = 0; |
| count++; |
| if (!Helper.IS_THOROUGH && count > Helper.TEST_LIMIT) { |
| System.out.println("test limit is exceeded"); |
| return null; |
| } |
| if (dropArgs <= currentMaxDrops) { |
| if (dropArgs == 0) { |
| if (Helper.IS_THOROUGH || Helper.RNG.nextBoolean()) { |
| ++dropArgs; |
| return createTest(); |
| } else if (Helper.IS_VERBOSE) { |
| System.out.printf( |
| "argsCount=%d : \"drop\" scenarios are skipped%n", |
| args); |
| } |
| } else { |
| ++dropArgs; |
| return createTest(); |
| } |
| } |
| |
| if (args < maxArgs) { |
| dropArgs = 0; |
| currentMaxDrops = Math.min(args, maxDrops); |
| ++args; |
| return createTest(); |
| } |
| return null; |
| } |
| |
| private CatchExceptionTest createTest() { |
| if (!Helper.IS_THOROUGH) { |
| return new CatchExceptionTest( |
| TestCase.CONSTRUCTORS.get(constructor++).get(), |
| Helper.RNG.nextBoolean(), args, dropArgs); |
| } else { |
| if (isVararg) { |
| isVararg = false; |
| return new CatchExceptionTest( |
| TestCase.CONSTRUCTORS.get(constructor++).get(), |
| isVararg, args, dropArgs); |
| } else { |
| isVararg = true; |
| return new CatchExceptionTest( |
| TestCase.CONSTRUCTORS.get(constructor).get(), |
| isVararg, args, dropArgs); |
| } |
| } |
| } |
| } |
| |
| class TestCase<T> { |
| private static enum ThrowMode { |
| NOTHING, |
| CAUGHT, |
| UNCAUGHT, |
| ADAPTER |
| } |
| |
| @SuppressWarnings("unchecked") |
| public static final List<Supplier<TestCase>> CONSTRUCTORS; |
| private static final MethodHandle FAKE_IDENTITY; |
| private static final MethodHandle THROW_OR_RETURN; |
| private static final MethodHandle CATCHER; |
| |
| static { |
| try { |
| MethodHandles.Lookup lookup = MethodHandles.lookup(); |
| THROW_OR_RETURN = lookup.findStatic( |
| TestCase.class, |
| "throwOrReturn", |
| MethodType.methodType(Object.class, Object.class, |
| Throwable.class) |
| ); |
| CATCHER = lookup.findStatic( |
| TestCase.class, |
| "catcher", |
| MethodType.methodType(Object.class, Object.class)); |
| FAKE_IDENTITY = lookup.findVirtual( |
| TestCase.class, "fakeIdentity", |
| MethodType.methodType(Object.class, Object.class)); |
| |
| } catch (NoSuchMethodException | IllegalAccessException e) { |
| throw new Error(e); |
| } |
| PartialConstructor[] constructors = { |
| create(Object.class, Object.class::cast), |
| create(String.class, Objects::toString), |
| create(int[].class, x -> new int[]{Objects.hashCode(x)}), |
| create(long.class, |
| x -> Objects.hashCode(x) & (-1L >>> 32)), |
| create(void.class, TestCase::noop)}; |
| Throwable[] throwables = { |
| new ClassCastException("testing"), |
| new java.io.IOException("testing"), |
| new LinkageError("testing")}; |
| List<Supplier<TestCase>> list = new ArrayList<>(constructors.length |
| * throwables.length * ThrowMode.values().length); |
| //noinspection unchecked |
| for (PartialConstructor f : constructors) { |
| for (ThrowMode mode : ThrowMode.values()) { |
| for (Throwable t : throwables) { |
| list.add(f.apply(mode, t)); |
| } |
| } |
| } |
| CONSTRUCTORS = Collections.unmodifiableList(list); |
| } |
| |
| public final Class<T> rtype; |
| public final ThrowMode throwMode; |
| public final Throwable thrown; |
| public final Class<? extends Throwable> throwableClass; |
| /** |
| * MH which takes 2 args (Object,Throwable), 1st is the return value, |
| * 2nd is the exception which will be thrown, if it's supposed in current |
| * {@link #throwMode}. |
| */ |
| public final MethodHandle thrower; |
| private final Function<Object, T> cast; |
| protected MethodHandle filter; |
| private int fakeIdentityCount; |
| |
| private TestCase(Class<T> rtype, Function<Object, T> cast, |
| ThrowMode throwMode, Throwable thrown) |
| throws NoSuchMethodException, IllegalAccessException { |
| this.cast = cast; |
| filter = MethodHandles.lookup().findVirtual( |
| Function.class, |
| "apply", |
| MethodType.methodType(Object.class, Object.class)) |
| .bindTo(cast); |
| this.rtype = rtype; |
| this.throwMode = throwMode; |
| this.throwableClass = thrown.getClass(); |
| switch (throwMode) { |
| case NOTHING: |
| this.thrown = null; |
| break; |
| case ADAPTER: |
| case UNCAUGHT: |
| this.thrown = new Error("do not catch this"); |
| break; |
| default: |
| this.thrown = thrown; |
| } |
| |
| MethodHandle throwOrReturn = THROW_OR_RETURN; |
| if (throwMode == ThrowMode.ADAPTER) { |
| MethodHandle fakeIdentity = FAKE_IDENTITY.bindTo(this); |
| for (int i = 0; i < 10; ++i) { |
| throwOrReturn = MethodHandles.filterReturnValue( |
| throwOrReturn, fakeIdentity); |
| } |
| } |
| thrower = throwOrReturn.asType(MethodType.genericMethodType(2)); |
| } |
| |
| private static Void noop(Object x) { |
| return null; |
| } |
| |
| private static <T2> PartialConstructor create( |
| Class<T2> rtype, Function<Object, T2> cast) { |
| return (t, u) -> () -> { |
| try { |
| return new TestCase<>(rtype, cast, t, u); |
| } catch (NoSuchMethodException | IllegalAccessException e) { |
| throw new Error(e); |
| } |
| }; |
| } |
| |
| private static <T extends Throwable> |
| Object throwOrReturn(Object normal, T exception) throws T { |
| if (exception != null) { |
| Helper.called("throwOrReturn/throw", normal, exception); |
| throw exception; |
| } |
| Helper.called("throwOrReturn/normal", normal, exception); |
| return normal; |
| } |
| |
| private static <T extends Throwable> |
| Object catcher(Object o) { |
| Helper.called("catcher", o); |
| return o; |
| } |
| |
| public MethodHandle filter(MethodHandle target) { |
| return MethodHandles.filterReturnValue(target, filter); |
| } |
| |
| public MethodHandle getCatcher(List<Class<?>> classes) { |
| return MethodHandles.filterReturnValue(Helper.AS_LIST.asType( |
| MethodType.methodType(Object.class, classes)), |
| CATCHER |
| ); |
| } |
| |
| @Override |
| public String toString() { |
| return "TestCase{" + |
| "rtype=" + rtype + |
| ", throwMode=" + throwMode + |
| ", throwableClass=" + throwableClass + |
| '}'; |
| } |
| |
| public String callName() { |
| return "throwOrReturn/" + |
| (throwMode == ThrowMode.NOTHING |
| ? "normal" |
| : "throw"); |
| } |
| |
| public void assertReturn(Object returned, Object arg0, Object arg1, |
| int catchDrops, Object... args) { |
| int lag = 0; |
| if (throwMode == ThrowMode.CAUGHT) { |
| lag = 1; |
| } |
| Helper.assertCalled(lag, callName(), arg0, arg1); |
| |
| if (throwMode == ThrowMode.NOTHING) { |
| assertEQ(cast.apply(arg0), returned); |
| } else if (throwMode == ThrowMode.CAUGHT) { |
| List<Object> catchArgs = new ArrayList<>(Arrays.asList(args)); |
| // catcher receives an initial subsequence of target arguments: |
| catchArgs.subList(args.length - catchDrops, args.length).clear(); |
| // catcher also receives the exception, prepended: |
| catchArgs.add(0, thrown); |
| Helper.assertCalled("catcher", catchArgs); |
| assertEQ(cast.apply(catchArgs), returned); |
| } |
| Asserts.assertEQ(0, fakeIdentityCount); |
| } |
| |
| private void assertEQ(T t, Object returned) { |
| if (rtype.isArray()) { |
| Asserts.assertEQ(t.getClass(), returned.getClass()); |
| int n = Array.getLength(t); |
| Asserts.assertEQ(n, Array.getLength(returned)); |
| for (int i = 0; i < n; ++i) { |
| Asserts.assertEQ(Array.get(t, i), Array.get(returned, i)); |
| } |
| } else { |
| Asserts.assertEQ(t, returned); |
| } |
| } |
| |
| private Object fakeIdentity(Object x) { |
| System.out.println("should throw through this!"); |
| ++fakeIdentityCount; |
| return x; |
| } |
| |
| public void assertCatch(Throwable ex) { |
| try { |
| Asserts.assertSame(thrown, ex, |
| "must get the out-of-band exception"); |
| } catch (Throwable t) { |
| ex.printStackTrace(); |
| } |
| } |
| |
| public interface PartialConstructor |
| extends BiFunction<ThrowMode, Throwable, Supplier<TestCase>> { |
| } |
| } |