diff -r 318e66c8de0e -r 505c3a4eb0d6 jdk/test/java/lang/invoke/MethodHandles/CatchExceptionTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/lang/invoke/MethodHandles/CatchExceptionTest.java Sat Mar 29 12:29:21 2014 +0400 @@ -0,0 +1,542 @@ +/* + * Copyright (c) 2014, 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 com.oracle.testlibrary.jsr292.Helper; +import jdk.testlibrary.Asserts; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Array; +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +/* @test + * @library /lib/testlibrary/jsr292 /lib/testlibrary/ + * @compile CatchExceptionTest.java + * @run main/othervm -esa test.java.lang.invoke.MethodHandles.CatchExceptionTest + */ +public class CatchExceptionTest { + private static final List> 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, + }; + List> list = new ArrayList<>(MAX_ARITY); + for (int i = 0; i < MAX_ARITY; ++i) { + list.add(classes[Helper.RNG.nextInt(classes.length)]); + } + ARGS_CLASSES = Collections.unmodifiableList(list); + } + + 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; + if (Helper.IS_VERBOSE) { + System.out.printf("CatchException::CatchException(%s, isVararg=%b " + + "argsCount=%d catchDrops=%d)%n", + testCase, isVararg, argsCount, catchDrops + ); + } + MethodHandle thrower = testCase.thrower; + int throwerLen = thrower.type().parameterCount(); + List> 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 { + for (CatchExceptionTest test : TestFactory.MANDATORY_TEST_CASES) { + test.runTest(); + } + TestFactory factory = new TestFactory(); + CatchExceptionTest test; + while ((test = factory.nextTest()) != null ) { + test.runTest(); + } + } + + private List> getThrowerParams(boolean isVararg, int argsCount) { + boolean unmodifiable = true; + List> classes; + classes = ARGS_CLASSES.subList(0, + Math.min(argsCount, (MAX_ARITY / 2) - 1)); + int extra = 0; + if (argsCount >= MAX_ARITY / 2) { + classes = new ArrayList<>(classes); + unmodifiable = false; + extra = (int) classes.stream().filter(Helper::isDoubleCost).count(); + int i = classes.size(); + while (classes.size() + extra < argsCount) { + Class aClass = ARGS_CLASSES.get(i); + if (Helper.isDoubleCost(aClass)) { + ++extra; + if (classes.size() + extra >= argsCount) { + break; + } + } + classes.add(aClass); + } + } + if (isVararg && classes.size() > 0) { + if (unmodifiable) { + classes = new ArrayList<>(classes); + } + int last = classes.size() - 1; + Class aClass = classes.get(classes.size() - 1); + aClass = Array.newInstance(aClass, 2).getClass(); + classes.set(last, aClass); + } + return classes; + } + + + private List> getCatcherParams() { + int catchArgc = 1 + this.argsCount - dropped; + List> result = new ArrayList<>( + thrower.type().parameterList().subList(0, catchArgc - 1)); + // prepend throwable + result.add(0, testCase.throwableClass); + return result; + } + + private void runTest() { + 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) { + testCase.assertCatch(ex); + returned = ex; + } + + testCase.assertReturn(returned, arg0, arg1, dropped, args); + } +} + +class TestFactory { + public static final List 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; + } + + if (Helper.IS_VERBOSE) { + System.out.printf("maxArgs = %d%nmaxDrops = %d%n", + maxArgs, maxDrops); + } + constructorSize = TestCase.CONSTRUCTORS.size(); + } + + private static List createTests(int argsCount, + int catchDrops) { + if (catchDrops > argsCount || argsCount < 0 || catchDrops < 0) { + throw new IllegalArgumentException("argsCount = " + argsCount + + ", catchDrops = " + catchDrops + ); + } + List result = new ArrayList<>( + TestCase.CONSTRUCTORS.size()); + for (Supplier 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 {1, .., 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 == 1) { + 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 = 1; + 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 { + private static enum ThrowMode { + NOTHING, + CAUGHT, + UNCAUGHT, + ADAPTER + } + + @SuppressWarnings("unchecked") + public static final List> 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> 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 rtype; + public final ThrowMode throwMode; + public final Throwable thrown; + public final Class 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 cast; + protected MethodHandle filter; + private int fakeIdentityCount; + + private TestCase(Class rtype, Function 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 PartialConstructor create( + Class rtype, Function cast) { + return (t, u) -> () -> { + try { + return new TestCase<>(rtype, cast, t, u); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new Error(e); + } + }; + } + + private static + 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 + Object catcher(Object o) { + Helper.called("catcher", o); + return o; + } + + public MethodHandle filter(MethodHandle target) { + return MethodHandles.filterReturnValue(target, filter); + } + + public MethodHandle getCatcher(List> 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 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> { + } +}