| /* | |
| * Copyright (c) 2014, 2018, 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.test.lib.Asserts; | |
| import jdk.test.lib.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 /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>> { | |
| } | |
| } |