changeset 4383:668edf27e9c7

7058651: JSR 292 unit tests need a refresh Summary: Enhancements to unit tests. Reviewed-by: never, twisti
author jrose
date Sat, 16 Jul 2011 15:44:33 -0700
parents bfc5ec581c48
children b42029cd1744
files test/java/lang/invoke/JavaDocExamplesTest.java test/java/lang/invoke/MethodHandlesTest.java test/java/lang/invoke/RicochetTest.java test/java/lang/invoke/ThrowExceptionsTest.java
diffstat 4 files changed, 692 insertions(+), 89 deletions(-) [+]
line wrap: on
line diff
--- a/test/java/lang/invoke/JavaDocExamplesTest.java	Sat Jul 16 15:40:13 2011 -0700
+++ b/test/java/lang/invoke/JavaDocExamplesTest.java	Sat Jul 16 15:44:33 2011 -0700
@@ -69,6 +69,7 @@
         testDropArguments();
         testFilterArguments();
         testFoldArguments();
+        testFoldArguments2();
         testMethodHandlesSummary();
         testAsSpreader();
         testAsCollector();
@@ -490,6 +491,47 @@
             }}
     }
 
+    @Test public void testFoldArguments2() throws Throwable {
+        {{
+{} /// JAVADOC
+// argument-based dispatch for methods of the form boolean x.___(y: String)
+Lookup lookup = publicLookup();
+// first, a tracing hack:
+MethodHandle println = lookup.findVirtual(java.io.PrintStream.class, "println", methodType(void.class, String.class));
+MethodHandle arrayToString = lookup.findStatic(Arrays.class, "toString", methodType(String.class, Object[].class));
+MethodHandle concat = lookup.findVirtual(String.class, "concat", methodType(String.class, String.class));
+MethodHandle arrayToString_DIS = filterReturnValue(arrayToString, concat.bindTo("DIS:"));
+MethodHandle arrayToString_INV = filterReturnValue(arrayToString, concat.bindTo("INV:"));
+MethodHandle printArgs_DIS = filterReturnValue(arrayToString_DIS, println.bindTo(System.out)).asVarargsCollector(Object[].class);
+MethodHandle printArgs_INV = filterReturnValue(arrayToString_INV, println.bindTo(System.out)).asVarargsCollector(Object[].class);
+// metaobject protocol:
+MethodType mtype = methodType(boolean.class, String.class);
+MethodHandle findVirtual = lookup.findVirtual(Lookup.class,
+  "findVirtual", methodType(MethodHandle.class, Class.class, String.class, MethodType.class));
+MethodHandle getClass = lookup.findVirtual(Object.class,
+  "getClass", methodType(Class.class));
+MethodHandle dispatch = findVirtual;
+dispatch = filterArguments(dispatch, 1, getClass);
+dispatch = insertArguments(dispatch, 3, mtype);
+dispatch = dispatch.bindTo(lookup);
+assertEquals(methodType(MethodHandle.class, Object.class, String.class), dispatch.type());
+MethodHandle invoker = invoker(mtype.insertParameterTypes(0, Object.class));
+// wrap tracing around the dispatch and invoke steps:
+dispatch = foldArguments(dispatch, printArgs_DIS.asType(dispatch.type().changeReturnType(void.class)));
+invoker = foldArguments(invoker, printArgs_INV.asType(invoker.type().changeReturnType(void.class)));
+invoker = dropArguments(invoker, 2, String.class);  // ignore selector
+// compose the dispatcher and the invoker:
+MethodHandle invokeDispatched = foldArguments(invoker, dispatch);
+Object x = "football", y = new java.util.Scanner("bar");
+assert( (boolean) invokeDispatched.invokeExact(x, "startsWith", "foo"));
+assert(!(boolean) invokeDispatched.invokeExact(x, "startsWith", "#"));
+assert( (boolean) invokeDispatched.invokeExact(x, "endsWith", "all"));
+assert(!(boolean) invokeDispatched.invokeExact(x, "endsWith", "foo"));
+assert( (boolean) invokeDispatched.invokeExact(y, "hasNext", "[abc]+[rst]"));
+assert(!(boolean) invokeDispatched.invokeExact(y, "hasNext", "[123]+[789]"));
+            }}
+    }
+
     /* ---- TEMPLATE ----
     @Test public void testFoo() throws Throwable {
         {{
--- a/test/java/lang/invoke/MethodHandlesTest.java	Sat Jul 16 15:40:13 2011 -0700
+++ b/test/java/lang/invoke/MethodHandlesTest.java	Sat Jul 16 15:44:33 2011 -0700
@@ -37,7 +37,6 @@
 import java.util.*;
 import org.junit.*;
 import static org.junit.Assert.*;
-import static org.junit.Assume.*;
 
 
 /**
@@ -45,10 +44,13 @@
  * @author jrose
  */
 public class MethodHandlesTest {
+    static final Class<?> THIS_CLASS = MethodHandlesTest.class;
     // How much output?
     static int verbosity = 0;
     static {
-        String vstr = System.getProperty("test.java.lang.invoke.MethodHandlesTest.verbosity");
+        String vstr = System.getProperty(THIS_CLASS.getSimpleName()+".verbosity");
+        if (vstr == null)
+            vstr = System.getProperty(THIS_CLASS.getName()+".verbosity");
         if (vstr != null)  verbosity = Integer.parseInt(vstr);
     }
 
@@ -58,9 +60,9 @@
     static boolean CAN_SKIP_WORKING = false;
     //static { CAN_SKIP_WORKING = true; }
 
-    // Set true to test more calls.  If false, some tests are just
-    // lookups, without exercising the actual method handle.
-    static boolean DO_MORE_CALLS = true;
+    // Set 'true' to do about 15x fewer tests, especially those redundant with RicochetTest.
+    // This might be useful with -Xcomp stress tests that compile all method handles.
+    static boolean CAN_TEST_LIGHTLY = Boolean.getBoolean(THIS_CLASS.getName()+".CAN_TEST_LIGHTLY");
 
     @Test
     public void testFirst() throws Throwable {
@@ -70,37 +72,37 @@
     }
 
     // current failures
-    @Test @Ignore("failure in call to makeRawRetypeOnly in ToGeneric")
+    @Test //@Ignore("failure in call to makeRawRetypeOnly in ToGeneric")
     public void testFail_1() throws Throwable {
         // AMH.<init>: IllegalArgumentException: bad adapter (conversion=0xfffab300): adapter pushes too many parameters
         testSpreadArguments(int.class, 0, 6);
     }
-    @Test @Ignore("failure in JVM when expanding the stack using asm stub for _adapter_spread_args")
+    @Test //@Ignore("failure in JVM when expanding the stack using asm stub for _adapter_spread_args")
     public void testFail_2() throws Throwable {
         // if CONV_OP_IMPLEMENTED_MASK includes OP_SPREAD_ARGS, this crashes:
         testSpreadArguments(Object.class, 0, 2);
     }
-    @Test @Ignore("IllArgEx failure in call to ToGeneric.make")
+    @Test //@Ignore("IllArgEx failure in call to ToGeneric.make")
     public void testFail_3() throws Throwable {
         // ToGeneric.<init>: UnsupportedOperationException: NYI: primitive parameters must follow references; entryType = (int,java.lang.Object)java.lang.Object
         testSpreadArguments(int.class, 1, 2);
     }
-    @Test @Ignore("IllArgEx failure in call to ToGeneric.make")
+    @Test //@Ignore("IllArgEx failure in call to ToGeneric.make")
     public void testFail_4() throws Throwable {
         // ToGeneric.<init>: UnsupportedOperationException: NYI: primitive parameters must follow references; entryType = (int,java.lang.Object)java.lang.Object
         testCollectArguments(int.class, 1, 2);
     }
-    @Test @Ignore("cannot collect leading primitive types")
+    @Test //@Ignore("cannot collect leading primitive types")
     public void testFail_5() throws Throwable {
         // ToGeneric.<init>: UnsupportedOperationException: NYI: primitive parameters must follow references; entryType = (int,java.lang.Object)java.lang.Object
         testInvokers(MethodType.genericMethodType(2).changeParameterType(0, int.class));
     }
-    @Test @Ignore("should not insert arguments beyond MethodHandlePushLimit")
+    @Test //@Ignore("should not insert arguments beyond MethodHandlePushLimit")
     public void testFail_6() throws Throwable {
         // ValueConversions.varargsArray: UnsupportedOperationException: NYI: cannot form a varargs array of length 13
         testInsertArguments(0, 0, MAX_ARG_INCREASE+10);
     }
-    @Test @Ignore("permuteArguments has trouble with double slots")
+    @Test //@Ignore("permuteArguments has trouble with double slots")
     public void testFail_7() throws Throwable {
         testPermuteArguments(new Object[]{10, 200L},
                              new Class<?>[]{Integer.class, long.class},
@@ -123,7 +125,7 @@
         testPermuteArguments(new Object[]{10, 200L, 5000L},
                              new Class<?>[]{Integer.class, long.class, long.class},
                              new int[]{2,2,0,1});
-        testPermuteArguments(4, Integer.class,  2, long.class,    6);
+        //testPermuteArguments(4, Integer.class,  2, long.class,    6);
     }
     static final int MAX_ARG_INCREASE = 3;
 
@@ -167,7 +169,7 @@
     @AfterClass
     public static void tearDownClass() throws Exception {
         int posTests = allPosTests, negTests = allNegTests;
-        if (verbosity >= 2 && (posTests | negTests) != 0) {
+        if (verbosity >= 0 && (posTests | negTests) != 0) {
             System.out.println();
             if (posTests != 0)  System.out.println("=== "+posTests+" total positive test cases");
             if (negTests != 0)  System.out.println("=== "+negTests+" total negative test cases");
@@ -383,6 +385,13 @@
         MethodType ttype2 = MethodType.methodType(targetType.returnType(), argTypes);
         return target.asType(ttype2);
     }
+    static MethodHandle addTrailingArgs(MethodHandle target, int nargs, Class<?> argClass) {
+        int targetLen = target.type().parameterCount();
+        int extra = (nargs - targetLen);
+        if (extra <= 0)  return target;
+        List<Class<?>> fakeArgs = Collections.<Class<?>>nCopies(extra, argClass);
+        return MethodHandles.dropArguments(target, targetLen, fakeArgs);
+    }
 
     // This lookup is good for all members in and under MethodHandlesTest.
     static final Lookup PRIVATE = MethodHandles.lookup();
@@ -419,6 +428,10 @@
         public static Object   s6(int x, long y) { return called("s6", x, y); }
         public static Object   s7(float x, double y) { return called("s7", x, y); }
 
+        // for testing findConstructor:
+        public Example(String x, int y) { this.name = x+y; called("Example.<init>", x, y); }
+        public Example(int x, String y) { this.name = x+y; called("Example.<init>", x, y); }
+
         static final Lookup EXAMPLE = MethodHandles.lookup();  // for testing findSpecial
     }
     static final Lookup EXAMPLE = Example.EXAMPLE;
@@ -521,7 +534,6 @@
         if (!positive)  return; // negative test failed as expected
         assertEquals(type, target.type());
         assertNameStringContains(target, name);
-        if (!DO_MORE_CALLS && lookup != PRIVATE)  return;
         Object[] args = randomArgs(params);
         printCalled(target, name, args);
         target.invokeWithArguments(args);
@@ -604,7 +616,6 @@
         MethodType typeWithSelf = MethodType.methodType(ret, paramsWithSelf);
         assertEquals(typeWithSelf, target.type());
         assertNameStringContains(target, methodName);
-        if (!DO_MORE_CALLS && lookup != PRIVATE)  return;
         Object[] argsWithSelf = randomArgs(paramsWithSelf);
         if (rcvc != defc)  argsWithSelf[0] = randomArg(rcvc);
         printCalled(target, name, argsWithSelf);
@@ -666,7 +677,6 @@
         Class<?>[] paramsWithSelf = cat(array(Class[].class, (Class)specialCaller), params);
         MethodType typeWithSelf = MethodType.methodType(ret, paramsWithSelf);
         assertNameStringContains(target, name);
-        if (!DO_MORE_CALLS && lookup != PRIVATE && lookup != EXAMPLE)  return;
         Object[] args = randomArgs(paramsWithSelf);
         printCalled(target, name, args);
         target.invokeWithArguments(args);
@@ -674,6 +684,43 @@
     }
 
     @Test
+    public void testFindConstructor() throws Throwable {
+        if (CAN_SKIP_WORKING)  return;
+        startTest("findConstructor");
+        testFindConstructor(true, EXAMPLE, Example.class);
+        testFindConstructor(true, EXAMPLE, Example.class, int.class);
+        testFindConstructor(true, EXAMPLE, Example.class, String.class);
+    }
+    void testFindConstructor(boolean positive, Lookup lookup,
+                             Class<?> defc, Class<?>... params) throws Throwable {
+        countTest(positive);
+        MethodType type = MethodType.methodType(void.class, params);
+        MethodHandle target = null;
+        Exception noAccess = null;
+        try {
+            if (verbosity >= 4)  System.out.println("lookup via "+lookup+" of "+defc+" <init>"+type);
+            target = lookup.findConstructor(defc, type);
+        } catch (ReflectiveOperationException ex) {
+            noAccess = ex;
+            assertTrue(noAccess instanceof IllegalAccessException);
+        }
+        if (verbosity >= 3)
+            System.out.println("findConstructor "+defc.getName()+".<init>/"+type+" => "+target
+                               +(target == null ? "" : target.type())
+                               +(noAccess == null ? "" : " !! "+noAccess));
+        if (positive && noAccess != null)  throw noAccess;
+        assertEquals(positive ? "positive test" : "negative test erroneously passed", positive, target != null);
+        if (!positive)  return; // negative test failed as expected
+        assertEquals(type.changeReturnType(defc), target.type());
+        Object[] args = randomArgs(params);
+        printCalled(target, defc.getSimpleName(), args);
+        Object obj = target.invokeWithArguments(args);
+        if (!(defc == Example.class && params.length < 2))
+            assertCalled(defc.getSimpleName()+".<init>", args);
+        assertTrue("instance of "+defc.getName(), defc.isInstance(obj));
+    }
+
+    @Test
     public void testBind() throws Throwable {
         if (CAN_SKIP_WORKING)  return;
         startTest("bind");
@@ -956,6 +1003,8 @@
 
     public void testAccessor(boolean positive, MethodHandles.Lookup lookup,
                              Object fieldRef, Object value, int testMode0) throws Throwable {
+        if (verbosity >= 4)
+            System.out.println("testAccessor"+Arrays.asList(positive, lookup, fieldRef, value, testMode0));
         boolean isGetter = ((testMode0 & TEST_SETTER) == 0);
         int testMode = testMode0 & ~TEST_SETTER;
         boolean isStatic;
@@ -1150,7 +1199,7 @@
 
     public void testArrayElementGetterSetter(Object array, boolean testSetter) throws Throwable {
         countTest(true);
-        if (verbosity >= 2)  System.out.println("array type = "+array.getClass().getComponentType().getName()+"["+Array.getLength(array)+"]");
+        if (verbosity > 2)  System.out.println("array type = "+array.getClass().getComponentType().getName()+"["+Array.getLength(array)+"]");
         Class<?> arrayType = array.getClass();
         Class<?> elemType = arrayType.getComponentType();
         MethodType expType = !testSetter
@@ -1326,9 +1375,10 @@
     public void testPermuteArguments() throws Throwable {
         if (CAN_SKIP_WORKING)  return;
         startTest("permuteArguments");
+        testPermuteArguments(4, Integer.class,  2, long.class,    6);
+        if (CAN_TEST_LIGHTLY)  return;
         testPermuteArguments(4, Integer.class,  2, String.class,  0);
         testPermuteArguments(6, Integer.class,  0, null,         30);
-        //testPermuteArguments(4, Integer.class,  2, long.class,    6);  // FIXME Fail_7
     }
     public void testPermuteArguments(int max, Class<?> type1, int t2c, Class<?> type2, int dilution) throws Throwable {
         if (verbosity >= 2)
@@ -1354,7 +1404,9 @@
                     casStep++;
                 testPermuteArguments(args, types, outargs, numcases, casStep);
                 numcases *= inargs;
+                if (CAN_TEST_LIGHTLY && outargs < max-2)  continue;
                 if (dilution > 10 && outargs >= 4) {
+                    if (CAN_TEST_LIGHTLY)  continue;
                     int[] reorder = new int[outargs];
                     // Do some special patterns, which we probably missed.
                     // Replication of a single argument or argument pair.
@@ -1392,6 +1444,7 @@
                 reorder[i] = c % inargs;
                 c /= inargs;
             }
+            if (CAN_TEST_LIGHTLY && outargs >= 3 && (reorder[0] == reorder[1] || reorder[1] == reorder[2]))  continue;
             testPermuteArguments(args, types, reorder);
         }
     }
@@ -1464,12 +1517,13 @@
         for (Class<?> argType : new Class[]{Object.class, Integer.class, int.class}) {
             if (verbosity >= 3)
                 System.out.println("spreadArguments "+argType);
-            // FIXME: enable _adapter_spread_args and fix Fail_2
-            for (int nargs = 0; nargs < 10; nargs++) {
-                if (argType == int.class && nargs >= 6)  continue; // FIXME Fail_1
-                for (int pos = 0; pos < nargs; pos++) {
-                    if (argType == int.class && pos > 0)  continue; // FIXME Fail_3
-                     testSpreadArguments(argType, pos, nargs);
+            for (int nargs = 0; nargs < 50; nargs++) {
+                if (CAN_TEST_LIGHTLY && nargs > 7)  break;
+                for (int pos = 0; pos <= nargs; pos++) {
+                    if (CAN_TEST_LIGHTLY && pos > 2 && pos < nargs-2)  continue;
+                    if (nargs > 10 && pos > 4 && pos < nargs-4 && pos % 10 != 3)
+                        continue;
+                    testSpreadArguments(argType, pos, nargs);
                 }
             }
         }
@@ -1557,9 +1611,12 @@
         for (Class<?> argType : new Class[]{Object.class, Integer.class, int.class}) {
             if (verbosity >= 3)
                 System.out.println("collectArguments "+argType);
-            for (int nargs = 0; nargs < 10; nargs++) {
-                for (int pos = 0; pos < nargs; pos++) {
-                    if (argType == int.class)  continue; // FIXME Fail_4
+            for (int nargs = 0; nargs < 50; nargs++) {
+                if (CAN_TEST_LIGHTLY && nargs > 7)  break;
+                for (int pos = 0; pos <= nargs; pos++) {
+                    if (CAN_TEST_LIGHTLY && pos > 2 && pos < nargs-2)  continue;
+                    if (nargs > 10 && pos > 4 && pos < nargs-4 && pos % 10 != 3)
+                        continue;
                     testCollectArguments(argType, pos, nargs);
                 }
             }
@@ -1593,10 +1650,15 @@
     public void testInsertArguments() throws Throwable {
         if (CAN_SKIP_WORKING)  return;
         startTest("insertArguments");
-        for (int nargs = 0; nargs <= 4; nargs++) {
-            for (int ins = 0; ins <= 4; ins++) {
-                if (ins > MAX_ARG_INCREASE)  continue;  // FIXME Fail_6
+        for (int nargs = 0; nargs < 50; nargs++) {
+            if (CAN_TEST_LIGHTLY && nargs > 7)  break;
+            for (int ins = 0; ins <= nargs; ins++) {
+                if (nargs > 10 && ins > 4 && ins < nargs-4 && ins % 10 != 3)
+                    continue;
                 for (int pos = 0; pos <= nargs; pos++) {
+                    if (nargs > 10 && pos > 4 && pos < nargs-4 && pos % 10 != 3)
+                        continue;
+                    if (CAN_TEST_LIGHTLY && pos > 2 && pos < nargs-2)  continue;
                     testInsertArguments(nargs, pos, ins);
                 }
             }
@@ -1634,8 +1696,8 @@
             for (Class<?> rtype : new Class[] { Object.class,
                                                 List.class,
                                                 int.class,
-                                                //byte.class, //FIXME: add this
-                                                //long.class, //FIXME: add this
+                                                byte.class,
+                                                long.class,
                                                 CharSequence.class,
                                                 String.class }) {
                 testFilterReturnValue(nargs, rtype);
@@ -1780,6 +1842,7 @@
         // exactInvoker, genericInvoker, varargsInvoker[0..N], dynamicInvoker
         Set<MethodType> done = new HashSet<MethodType>();
         for (int i = 0; i <= 6; i++) {
+            if (CAN_TEST_LIGHTLY && i > 3)  break;
             MethodType gtype = MethodType.genericMethodType(i);
             for (Class<?> argType : new Class[]{Object.class, Integer.class, int.class}) {
                 for (int j = -1; j < i; j++) {
@@ -1790,7 +1853,6 @@
                         continue;
                     else
                         type = type.changeParameterType(j, argType);
-                    if (argType.isPrimitive() && j != i-1)  continue; // FIXME Fail_5
                     if (done.add(type))
                         testInvokers(type);
                     MethodType vtype = type.changeReturnType(void.class);
@@ -1890,6 +1952,7 @@
         }
         for (int k = 0; k <= nargs; k++) {
             // varargs invoker #0..N
+            if (CAN_TEST_LIGHTLY && (k > 1 || k < nargs - 1))  continue;
             countTest();
             calledLog.clear();
             inv = MethodHandles.spreadInvoker(type, k);
@@ -1933,6 +1996,7 @@
     }
 
     private static final String MISSING_ARG = "missingArg";
+    private static final String MISSING_ARG_2 = "missingArg#2";
     static Object targetIfEquals() {
         return called("targetIfEquals");
     }
@@ -1968,28 +2032,39 @@
     public void testGuardWithTest() throws Throwable {
         if (CAN_SKIP_WORKING)  return;
         startTest("guardWithTest");
-        for (int nargs = 0; nargs <= 3; nargs++) {
-            if (nargs != 2)  continue;  // FIXME: test more later
+        for (int nargs = 0; nargs <= 50; nargs++) {
+            if (CAN_TEST_LIGHTLY && nargs > 7)  break;
             testGuardWithTest(nargs, Object.class);
             testGuardWithTest(nargs, String.class);
         }
     }
     void testGuardWithTest(int nargs, Class<?> argClass) throws Throwable {
+        testGuardWithTest(nargs, 0, argClass);
+        if (nargs <= 5 || nargs % 10 == 3) {
+            for (int testDrops = 1; testDrops <= nargs; testDrops++)
+                testGuardWithTest(nargs, testDrops, argClass);
+        }
+    }
+    void testGuardWithTest(int nargs, int testDrops, Class<?> argClass) throws Throwable {
         countTest();
+        int nargs1 = Math.min(3, nargs);
         MethodHandle test = PRIVATE.findVirtual(Object.class, "equals", MethodType.methodType(boolean.class, Object.class));
-        MethodHandle target = PRIVATE.findStatic(MethodHandlesTest.class, "targetIfEquals", MethodType.genericMethodType(nargs));
-        MethodHandle fallback = PRIVATE.findStatic(MethodHandlesTest.class, "fallbackIfNotEquals", MethodType.genericMethodType(nargs));
-        while (test.type().parameterCount() < nargs)
-            test = MethodHandles.dropArguments(test, test.type().parameterCount()-1, Object.class);
+        MethodHandle target = PRIVATE.findStatic(MethodHandlesTest.class, "targetIfEquals", MethodType.genericMethodType(nargs1));
+        MethodHandle fallback = PRIVATE.findStatic(MethodHandlesTest.class, "fallbackIfNotEquals", MethodType.genericMethodType(nargs1));
         while (test.type().parameterCount() > nargs)
+            // 0: test = constant(MISSING_ARG.equals(MISSING_ARG))
+            // 1: test = lambda (_) MISSING_ARG.equals(_)
             test = MethodHandles.insertArguments(test, 0, MISSING_ARG);
         if (argClass != Object.class) {
             test = changeArgTypes(test, argClass);
             target = changeArgTypes(target, argClass);
             fallback = changeArgTypes(fallback, argClass);
         }
-        MethodHandle mh = MethodHandles.guardWithTest(test, target, fallback);
-        assertEquals(target.type(), mh.type());
+        int testArgs = nargs - testDrops;
+        assert(testArgs >= 0);
+        test = addTrailingArgs(test, Math.min(testArgs, nargs), argClass);
+        target = addTrailingArgs(target, nargs, argClass);
+        fallback = addTrailingArgs(fallback, nargs, argClass);
         Object[][] argLists = {
             { },
             { "foo" }, { MISSING_ARG },
@@ -1997,7 +2072,19 @@
             { "foo", "foo", "baz" }, { "foo", "bar", "baz" }
         };
         for (Object[] argList : argLists) {
-            if (argList.length != nargs)  continue;
+            Object[] argList1 = argList;
+            if (argList.length != nargs) {
+                if (argList.length != nargs1)  continue;
+                argList1 = Arrays.copyOf(argList, nargs);
+                Arrays.fill(argList1, nargs1, nargs, MISSING_ARG_2);
+            }
+            MethodHandle test1 = test;
+            if (test1.type().parameterCount() > testArgs) {
+                int pc = test1.type().parameterCount();
+                test1 = MethodHandles.insertArguments(test, testArgs, Arrays.copyOfRange(argList1, testArgs, pc));
+            }
+            MethodHandle mh = MethodHandles.guardWithTest(test1, target, fallback);
+            assertEquals(target.type(), mh.type());
             boolean equals;
             switch (nargs) {
             case 0:   equals = true; break;
@@ -2007,7 +2094,7 @@
             String willCall = (equals ? "targetIfEquals" : "fallbackIfNotEquals");
             if (verbosity >= 3)
                 System.out.println(logEntry(willCall, argList));
-            Object result = mh.invokeWithArguments(argList);
+            Object result = mh.invokeWithArguments(argList1);
             assertCalled(willCall, argList);
         }
     }
@@ -2016,49 +2103,102 @@
     public void testCatchException() throws Throwable {
         if (CAN_SKIP_WORKING)  return;
         startTest("catchException");
-        for (int nargs = 2; nargs <= 6; nargs++) {
-            for (int ti = 0; ti <= 1; ti++) {
-                boolean throwIt = (ti != 0);
-                testCatchException(int.class, new ClassCastException("testing"), throwIt, nargs);
-                testCatchException(void.class, new java.io.IOException("testing"), throwIt, nargs);
-                testCatchException(String.class, new LinkageError("testing"), throwIt, nargs);
+        for (int nargs = 0; nargs < 40; nargs++) {
+            if (CAN_TEST_LIGHTLY && nargs > 7)  break;
+            for (int throwMode = 0; throwMode < THROW_MODE_LIMIT; throwMode++) {
+                testCatchException(int.class, new ClassCastException("testing"), throwMode, nargs);
+                if (CAN_TEST_LIGHTLY && nargs > 3)  continue;
+                testCatchException(void.class, new java.io.IOException("testing"), throwMode, nargs);
+                testCatchException(String.class, new LinkageError("testing"), throwMode, nargs);
             }
         }
     }
 
+    static final int THROW_NOTHING = 0, THROW_CAUGHT = 1, THROW_UNCAUGHT = 2, THROW_THROUGH_ADAPTER = 3, THROW_MODE_LIMIT = 4;
+
+    void testCatchException(Class<?> returnType, Throwable thrown, int throwMode, int nargs) throws Throwable {
+        testCatchException(returnType, thrown, throwMode, nargs, 0);
+        if (nargs <= 5 || nargs % 10 == 3) {
+            for (int catchDrops = 1; catchDrops <= nargs; catchDrops++)
+                testCatchException(returnType, thrown, throwMode, nargs, catchDrops);
+        }
+    }
+
     private static <T extends Throwable>
     Object throwOrReturn(Object normal, T exception) throws T {
-        if (exception != null)  throw exception;
+        if (exception != null) {
+            called("throwOrReturn/throw", normal, exception);
+            throw exception;
+        }
+        called("throwOrReturn/normal", normal, exception);
         return normal;
     }
+    private int fakeIdentityCount;
+    private Object fakeIdentity(Object x) {
+        System.out.println("should throw through this!");
+        fakeIdentityCount++;
+        return x;
+    }
 
-    void testCatchException(Class<?> returnType, Throwable thrown, boolean throwIt, int nargs) throws Throwable {
+    void testCatchException(Class<?> returnType, Throwable thrown, int throwMode, int nargs, int catchDrops) throws Throwable {
         countTest();
         if (verbosity >= 3)
-            System.out.println("catchException rt="+returnType+" throw="+throwIt+" nargs="+nargs);
+            System.out.println("catchException rt="+returnType+" throw="+throwMode+" nargs="+nargs+" drops="+catchDrops);
         Class<? extends Throwable> exType = thrown.getClass();
+        if (throwMode > THROW_CAUGHT)  thrown = new UnsupportedOperationException("do not catch this");
         MethodHandle throwOrReturn
                 = PRIVATE.findStatic(MethodHandlesTest.class, "throwOrReturn",
                     MethodType.methodType(Object.class, Object.class, Throwable.class));
+        if (throwMode == THROW_THROUGH_ADAPTER) {
+            MethodHandle fakeIdentity
+                = PRIVATE.findVirtual(MethodHandlesTest.class, "fakeIdentity",
+                    MethodType.methodType(Object.class, Object.class)).bindTo(this);
+            for (int i = 0; i < 10; i++)
+                throwOrReturn = MethodHandles.filterReturnValue(throwOrReturn, fakeIdentity);
+        }
+        int nargs1 = Math.max(2, nargs);
         MethodHandle thrower = throwOrReturn.asType(MethodType.genericMethodType(2));
-        while (thrower.type().parameterCount() < nargs)
-            thrower = MethodHandles.dropArguments(thrower, thrower.type().parameterCount(), Object.class);
-        MethodHandle catcher = varargsList(1+nargs).asType(MethodType.genericMethodType(1+nargs));
-        MethodHandle target = MethodHandles.catchException(thrower,
-                thrown.getClass(), catcher);
+        thrower = addTrailingArgs(thrower, nargs, Object.class);
+        int catchArgc = 1 + nargs - catchDrops;
+        MethodHandle catcher = varargsList(catchArgc).asType(MethodType.genericMethodType(catchArgc));
+        Object[] args = randomArgs(nargs, Object.class);
+        Object arg0 = MISSING_ARG;
+        Object arg1 = (throwMode == THROW_NOTHING) ? (Throwable) null : thrown;
+        if (nargs > 0)  arg0 = args[0];
+        if (nargs > 1)  args[1] = arg1;
+        assertEquals(nargs1, thrower.type().parameterCount());
+        if (nargs < nargs1) {
+            Object[] appendArgs = { arg0, arg1 };
+            appendArgs = Arrays.copyOfRange(appendArgs, nargs, nargs1);
+            thrower = MethodHandles.insertArguments(thrower, nargs, appendArgs);
+        }
+        assertEquals(nargs, thrower.type().parameterCount());
+        MethodHandle target = MethodHandles.catchException(thrower, exType, catcher);
         assertEquals(thrower.type(), target.type());
+        assertEquals(nargs, target.type().parameterCount());
         //System.out.println("catching with "+target+" : "+throwOrReturn);
-        Object[] args = randomArgs(nargs, Object.class);
-        args[1] = (throwIt ? thrown : null);
-        Object returned = target.invokeWithArguments(args);
+        Object returned;
+        try {
+            returned = target.invokeWithArguments(args);
+        } catch (Throwable ex) {
+            assertSame("must get the out-of-band exception", thrown, ex);
+            if (throwMode <= THROW_CAUGHT)
+                assertEquals(THROW_UNCAUGHT, throwMode);
+            returned = ex;
+        }
+        assertCalled("throwOrReturn/"+(throwMode == THROW_NOTHING ? "normal" : "throw"), arg0, arg1);
         //System.out.println("return from "+target+" : "+returned);
-        if (!throwIt) {
-            assertSame(args[0], returned);
-        } else {
+        if (throwMode == THROW_NOTHING) {
+            assertSame(arg0, returned);
+        } else if (throwMode == THROW_CAUGHT) {
             List<Object> catchArgs = new ArrayList<Object>(Arrays.asList(args));
+            // catcher receives an initial subsequence of target arguments:
+            catchArgs.subList(nargs - catchDrops, nargs).clear();
+            // catcher also receives the exception, prepended:
             catchArgs.add(0, thrown);
             assertEquals(catchArgs, returned);
         }
+        assertEquals(0, fakeIdentityCount);
     }
 
     @Test
@@ -2093,6 +2233,48 @@
     }
 
     @Test
+    public void testInterfaceCast() throws Throwable {
+        for (Class<?> ctype : new Class<?>[]{ Object.class, String.class, CharSequence.class, Number.class, Iterable.class}) {
+            testInterfaceCast(ctype, false, false);
+            testInterfaceCast(ctype, true,  false);
+            testInterfaceCast(ctype, false, true);
+            testInterfaceCast(ctype, true,  true);
+        }
+    }
+    public void testInterfaceCast(Class<?> ctype, boolean doret, boolean docast) throws Throwable {
+        String str = "normal return value";
+        MethodHandle mh = MethodHandles.identity(String.class);
+        MethodType mt = mh.type();
+        if (doret)  mt = mt.changeReturnType(ctype);
+        else        mt = mt.changeParameterType(0, ctype);
+        if (docast) mh = MethodHandles.explicitCastArguments(mh, mt);
+        else        mh = mh.asType(mt);
+        // this bit is needed to make the interface types disappear for invokeWithArguments:
+        mh = MethodHandles.explicitCastArguments(mh, mt.generic());
+        boolean expectFail = !ctype.isInstance(str);
+        if (ctype.isInterface()) {
+            // special rules:  interfaces slide by more frequently
+            if (docast || !doret)  expectFail = false;
+        }
+        Object res;
+        try {
+            res = mh.invokeWithArguments(str);
+        } catch (Exception ex) {
+            res = ex;
+        }
+        boolean sawFail = !(res instanceof String);
+        if (sawFail != expectFail) {
+            System.out.println("*** testInterfaceCast: "+mh+" was "+mt+" => "+res+(docast ? " (explicitCastArguments)" : ""));
+        }
+        if (!sawFail) {
+            assertFalse(res.toString(), expectFail);
+            assertEquals(str, res);
+        } else {
+            assertTrue(res.toString(), expectFail);
+        }
+    }
+
+    @Test
     public void testCastFailure() throws Throwable {
         if (CAN_SKIP_WORKING)  return;
         startTest("testCastFailure");
@@ -2235,46 +2417,105 @@
         called("runForRunnable");
     }
     public interface Fooable {
-        Object foo(Fooable x, Object y);
-        // this is for randomArg:
-        public class Impl implements Fooable {
-            public Object foo(Fooable x, Object y) {
-                throw new RuntimeException("do not call");
-            }
-            final String name;
-            public Impl() { name = "Fooable#"+nextArg(); }
-            @Override public String toString() { return name; }
-        }
+        // overloads:
+        Object foo(Object x, String y);
+        List   foo(String x, int y);
+        Object foo(String x);
     }
-    static Object fooForFooable(Fooable x, Object y) {
-        return called("fooForFooable", x, y);
+    static Object fooForFooable(String x, Object... y) {
+        return called("fooForFooable/"+x, y);
     }
     public static class MyCheckedException extends Exception {
     }
     public interface WillThrow {
         void willThrow() throws MyCheckedException;
     }
+    /*non-public*/ interface PrivateRunnable {
+        public void run();
+    }
 
     @Test
-    public void testAsInstance() throws Throwable {
+    public void testAsInterfaceInstance() throws Throwable {
         if (CAN_SKIP_WORKING)  return;
+        startTest("testAsInterfaceInstance");
         Lookup lookup = MethodHandles.lookup();
+        // test typical case:  Runnable.run
         {
+            countTest();
+            if (verbosity >= 2)  System.out.println("Runnable");
             MethodType mt = MethodType.methodType(void.class);
             MethodHandle mh = lookup.findStatic(MethodHandlesTest.class, "runForRunnable", mt);
             Runnable proxy = MethodHandleProxies.asInterfaceInstance(Runnable.class, mh);
             proxy.run();
             assertCalled("runForRunnable");
         }
+        // well known single-name overloaded interface:  Appendable.append
         {
-            MethodType mt = MethodType.methodType(Object.class, Fooable.class, Object.class);
-            MethodHandle mh = lookup.findStatic(MethodHandlesTest.class, "fooForFooable", mt);
+            countTest();
+            if (verbosity >= 2)  System.out.println("Appendable");
+            ArrayList<List> appendResults = new ArrayList<List>();
+            MethodHandle append = lookup.bind(appendResults, "add", MethodType.methodType(boolean.class, Object.class));
+            append = append.asType(MethodType.methodType(void.class, List.class)); // specialize the type
+            MethodHandle asList = lookup.findStatic(Arrays.class, "asList", MethodType.methodType(List.class, Object[].class));
+            MethodHandle mh = MethodHandles.filterReturnValue(asList, append).asVarargsCollector(Object[].class);
+            Appendable proxy = MethodHandleProxies.asInterfaceInstance(Appendable.class, mh);
+            proxy.append("one");
+            proxy.append("two", 3, 4);
+            proxy.append('5');
+            assertEquals(Arrays.asList(Arrays.asList("one"),
+                                       Arrays.asList("two", 3, 4),
+                                       Arrays.asList('5')),
+                         appendResults);
+            if (verbosity >= 3)  System.out.println("appendResults="+appendResults);
+            appendResults.clear();
+            Formatter formatter = new Formatter(proxy);
+            String fmt = "foo str=%s char='%c' num=%d";
+            Object[] fmtArgs = { "str!", 'C', 42 };
+            String expect = String.format(fmt, fmtArgs);
+            formatter.format(fmt, fmtArgs);
+            String actual = "";
+            if (verbosity >= 3)  System.out.println("appendResults="+appendResults);
+            for (List l : appendResults) {
+                Object x = l.get(0);
+                switch (l.size()) {
+                case 1:  actual += x; continue;
+                case 3:  actual += ((String)x).substring((int)l.get(1), (int)l.get(2)); continue;
+                }
+                actual += l;
+            }
+            if (verbosity >= 3)  System.out.println("expect="+expect);
+            if (verbosity >= 3)  System.out.println("actual="+actual);
+            assertEquals(expect, actual);
+        }
+        // test case of an single name which is overloaded:  Fooable.foo(...)
+        {
+            if (verbosity >= 2)  System.out.println("Fooable");
+            MethodHandle mh = lookup.findStatic(MethodHandlesTest.class, "fooForFooable",
+                                                MethodType.methodType(Object.class, String.class, Object[].class));
             Fooable proxy = MethodHandleProxies.asInterfaceInstance(Fooable.class, mh);
-            Object[] args = randomArgs(mt.parameterArray());
-            Object result = proxy.foo((Fooable) args[0], args[1]);
-            assertCalled("fooForFooable", args);
-            assertEquals(result, logEntry("fooForFooable", args));
+            for (Method m : Fooable.class.getDeclaredMethods()) {
+                countTest();
+                assertSame("foo", m.getName());
+                if (verbosity > 3)
+                    System.out.println("calling "+m);
+                MethodHandle invoker = lookup.unreflect(m);
+                MethodType mt = invoker.type();
+                Class<?>[] types = mt.parameterArray();
+                types[0] = int.class;  // placeholder
+                Object[] args = randomArgs(types);
+                args[0] = proxy;
+                if (verbosity > 3)
+                    System.out.println("calling "+m+" on "+Arrays.asList(args));
+                Object result = invoker.invokeWithArguments(args);
+                if (verbosity > 4)
+                    System.out.println("result = "+result);
+                String name = "fooForFooable/"+args[1];
+                Object[] argTail = Arrays.copyOfRange(args, 2, args.length);
+                assertCalled(name, argTail);
+                assertEquals(result, logEntry(name, argTail));
+            }
         }
+        // test processing of thrown exceptions:
         for (Throwable ex : new Throwable[] { new NullPointerException("ok"),
                                               new InternalError("ok"),
                                               new Throwable("fail"),
@@ -2285,11 +2526,12 @@
             mh = MethodHandles.insertArguments(mh, 0, ex);
             WillThrow proxy = MethodHandleProxies.asInterfaceInstance(WillThrow.class, mh);
             try {
+                countTest();
                 proxy.willThrow();
                 System.out.println("Failed to throw: "+ex);
                 assertTrue(false);
             } catch (Throwable ex1) {
-                if (verbosity > 2) {
+                if (verbosity > 3) {
                     System.out.println("throw "+ex);
                     System.out.println("catch "+(ex == ex1 ? "UNWRAPPED" : ex1));
                 }
@@ -2308,16 +2550,49 @@
                 }
             }
         }
-        // Test error checking:
-        for (Class<?> nonSAM : new Class[] { Object.class,
+        // Test error checking on bad interfaces:
+        for (Class<?> nonSMI : new Class[] { Object.class,
                                              String.class,
                                              CharSequence.class,
+                                             java.io.Serializable.class,
+                                             PrivateRunnable.class,
                                              Example.class }) {
+            if (verbosity > 2)  System.out.println(nonSMI.getName());
             try {
-                MethodHandleProxies.asInterfaceInstance(nonSAM, varargsArray(0));
-                System.out.println("Failed to throw");
-                assertTrue(false);
+                countTest(false);
+                MethodHandleProxies.asInterfaceInstance(nonSMI, varargsArray(0));
+                assertTrue("Failed to throw on "+nonSMI.getName(), false);
             } catch (IllegalArgumentException ex) {
+                if (verbosity > 2)  System.out.println(nonSMI.getSimpleName()+": "+ex);
+                // Object: java.lang.IllegalArgumentException:
+                //     not a public interface: java.lang.Object
+                // String: java.lang.IllegalArgumentException:
+                //     not a public interface: java.lang.String
+                // CharSequence: java.lang.IllegalArgumentException:
+                //     not a single-method interface: java.lang.CharSequence
+                // Serializable: java.lang.IllegalArgumentException:
+                //     not a single-method interface: java.io.Serializable
+                // PrivateRunnable: java.lang.IllegalArgumentException:
+                //     not a public interface: test.java.lang.invoke.MethodHandlesTest$PrivateRunnable
+                // Example: java.lang.IllegalArgumentException:
+                //     not a public interface: test.java.lang.invoke.MethodHandlesTest$Example
+            }
+        }
+        // Test error checking on interfaces with the wrong method type:
+        for (Class<?> intfc : new Class[] { Runnable.class /*arity 0*/,
+                                            Fooable.class /*arity 1 & 2*/ }) {
+            int badArity = 1;  // known to be incompatible
+            if (verbosity > 2)  System.out.println(intfc.getName());
+            try {
+                countTest(false);
+                MethodHandleProxies.asInterfaceInstance(intfc, varargsArray(badArity));
+                assertTrue("Failed to throw on "+intfc.getName(), false);
+            } catch (WrongMethodTypeException ex) {
+                if (verbosity > 2)  System.out.println(intfc.getSimpleName()+": "+ex);
+                // Runnable: java.lang.invoke.WrongMethodTypeException:
+                //     cannot convert MethodHandle(Object)Object[] to ()void
+                // Fooable: java.lang.invoke.WrongMethodTypeException:
+                //     cannot convert MethodHandle(Object)Object[] to (Object,String)Object
             }
         }
     }
--- a/test/java/lang/invoke/RicochetTest.java	Sat Jul 16 15:40:13 2011 -0700
+++ b/test/java/lang/invoke/RicochetTest.java	Sat Jul 16 15:44:33 2011 -0700
@@ -82,6 +82,7 @@
         testLongSpreads();
         testIntCollects();
         testReturns();
+        testRecursion();
     }
 
     @Test
@@ -371,6 +372,61 @@
         //System.out.println("faultCount="+faultCount);
     }
 
+    @Test
+    public void testRecursion() throws Throwable {
+        if (!startTest("testRecursion"))  return;
+        final int LIMIT = 10;
+        for (int i = 0; i < LIMIT; i++) {
+            RFCB rfcb = new RFCB(i);
+            Object x = "x", y = "y";
+            Object result = rfcb.recursiveFunction(x, y);
+            verbose(1, result);
+        }
+    }
+    /** Recursive Function Control Block */
+    private static class RFCB {
+        java.util.Random random;
+        final MethodHandle[] fns;
+        int depth;
+        RFCB(int seed) throws Throwable {
+            this.random = new java.util.Random(seed);
+            this.fns = new MethodHandle[Math.max(29, (1 << MAX_DEPTH-2)/3)];
+            java.util.Arrays.fill(fns, lookup().bind(this, "recursiveFunction", genericMethodType(2)));
+            for (int i = 5; i < fns.length; i++) {
+                switch (i % 4) {
+                case 0: fns[i] = filterArguments(fns[i - 5], 0, insertArguments(fns[i - 4], 1, ".")); break;
+                case 1: fns[i] = filterArguments(fns[i - 5], 1, insertArguments(fns[i - 3], 1, ".")); break;
+                case 2: fns[i] = filterReturnValue(fns[i - 5], insertArguments(fns[i - 2], 1, ".")); break;
+                }
+            }
+        }
+        Object recursiveFunction(Object x, Object y) throws Throwable {
+            depth++;
+            try {
+                final int ACTION_COUNT = 11;
+                switch (random.nextInt(ACTION_COUNT)) {
+                case 1:
+                    Throwable ex = new RuntimeException();
+                    ex.fillInStackTrace();
+                    if (VERBOSITY >= 2) ex.printStackTrace();
+                    x = "ST; " + x;
+                    break;
+                case 2:
+                    System.gc();
+                    x = "GC; " + x;
+                    break;
+                }
+                boolean isLeaf = (depth >= MAX_DEPTH);
+                if (isLeaf) {
+                    return Arrays.asList(x, y).toString();
+                }
+                return fns[random.nextInt(fns.length)].invokeExact(x, y);
+            } finally {
+                depth--;
+            }
+        }
+    }
+
     private static MethodHandle sequence(MethodHandle mh1, MethodHandle... mhs) {
         MethodHandle res = mh1;
         for (MethodHandle mh2 : mhs)
@@ -536,6 +592,7 @@
     }
 
     // stress modes:
+    private static final int MAX_DEPTH = getProperty("MAX_DEPTH", 5);
     private static final int REPEAT = getProperty("REPEAT", 0);
     private static final int STRESS = getProperty("STRESS", 0);
     private static /*v*/ int STRESS_COUNT;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/invoke/ThrowExceptionsTest.java	Sat Jul 16 15:44:33 2011 -0700
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2011, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+/* @test
+ * @summary unit tests for method handles which permute their arguments
+ * @run junit test.java.lang.invoke.ThrowExceptionsTest
+ */
+
+package test.java.lang.invoke;
+
+import org.junit.*;
+
+import java.util.*;
+import java.lang.reflect.*;
+
+import java.lang.invoke.*;
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+
+public class ThrowExceptionsTest {
+    private static final Class CLASS = ThrowExceptionsTest.class;
+    private static final Lookup LOOKUP = lookup();
+
+    public static void main(String argv[]) throws Throwable {
+        new ThrowExceptionsTest().testAll((argv.length == 0 ? null : Arrays.asList(argv).toString()));
+    }
+
+    @Test
+    public void testWMT() throws Throwable {
+        // mostly call testWMTCallee, but sometimes call its void-returning variant
+        MethodHandle mh = testWMTCallee();
+        MethodHandle mh1 = mh.asType(mh.type().changeReturnType(void.class));
+        assert(mh1 != mh);
+        testWMT(mh, mh1, 1000);
+    }
+
+    @Test
+    public void testBoundWMT() throws Throwable {
+        // mostly call exactInvoker.bindTo(testWMTCallee), but sometimes call its void-returning variant
+        MethodHandle callee = testWMTCallee();
+        MethodHandle callee1 = callee.asType(callee.type().changeReturnType(void.class));
+        MethodHandle invoker = exactInvoker(callee.type());
+        MethodHandle mh  = invoker.bindTo(callee);
+        MethodHandle mh1 = invoker.bindTo(callee1);
+        testWMT(mh, mh1, 1000);
+    }
+
+    @Test
+    public void testFoldWMT() throws Throwable {
+        // mostly call exactInvoker.fold(constant(testWMTCallee)), but sometimes call its void-returning variant
+        MethodHandle callee = testWMTCallee();
+        MethodHandle callee1 = callee.asType(callee.type().changeReturnType(void.class));
+        MethodHandle invoker = exactInvoker(callee.type());
+        MethodHandle mh  = foldArguments(invoker, constant(MethodHandle.class, callee));
+        MethodHandle mh1 = foldArguments(invoker, constant(MethodHandle.class, callee1));
+        testWMT(mh, mh1, 1000);
+    }
+
+    @Test
+    public void testFoldCCE() throws Throwable {
+        MethodHandle callee = testWMTCallee();
+        MethodHandle callee1 = callee.asType(callee.type().changeParameterType(1, Number.class)).asType(callee.type());
+        MethodHandle invoker = exactInvoker(callee.type());
+        MethodHandle mh  = foldArguments(invoker, constant(MethodHandle.class, callee));
+        MethodHandle mh1 = foldArguments(invoker, constant(MethodHandle.class, callee1));
+        testWMT(mh, mh1, 1000);
+    }
+
+    @Test
+    public void testStackOverflow() throws Throwable {
+        MethodHandle callee = testWMTCallee();
+        MethodHandle callee1 = makeStackOverflow().asType(callee.type());
+        MethodHandle invoker = exactInvoker(callee.type());
+        MethodHandle mh  = foldArguments(invoker, constant(MethodHandle.class, callee));
+        MethodHandle mh1 = foldArguments(invoker, constant(MethodHandle.class, callee1));
+        for (int i = 0; i < REPEAT; i++) {
+            try {
+                testWMT(mh, mh1, 1000);
+            } catch (StackOverflowError ex) {
+                // OK, try again
+            }
+        }
+    }
+
+    private static MethodHandle makeStackOverflow() {
+        MethodType cellType = methodType(void.class);
+        MethodHandle[] cell = { null };  // recursion point
+        MethodHandle getCell = insertArguments(arrayElementGetter(cell.getClass()), 0, cell, 0);
+        MethodHandle invokeCell = foldArguments(exactInvoker(cellType), getCell);
+        assert(invokeCell.type() == cellType);
+        cell[0] = invokeCell;
+        // make it conformable to any type:
+        invokeCell = dropArguments(invokeCell, 0, Object[].class).asVarargsCollector(Object[].class);
+        return invokeCell;
+    }
+
+    static int testCases;
+
+    private void testAll(String match) throws Throwable {
+        testCases = 0;
+        Lookup lookup = lookup();
+        for (Method m : CLASS.getDeclaredMethods()) {
+            String name = m.getName();
+            if (name.startsWith("test") &&
+                (match == null || match.contains(name.substring("test".length()))) &&
+                m.getParameterTypes().length == 0 &&
+                Modifier.isPublic(m.getModifiers()) &&
+                !Modifier.isStatic(m.getModifiers())) {
+                System.out.println("["+name+"]");
+                int tc = testCases;
+                try {
+                    m.invoke(this);
+                } catch (Throwable ex) {
+                    System.out.println("*** "+ex);
+                    ex.printStackTrace();
+                }
+                if (testCases == tc)  testCases++;
+            }
+        }
+        if (testCases == 0)  throw new RuntimeException("no test cases found");
+        System.out.println("ran a total of "+testCases+" test cases");
+    }
+
+    private static MethodHandle findStatic(String name) {
+        return findMethod(name, true);
+    }
+    private static MethodHandle findVirtual(String name) {
+        return findMethod(name, false);
+    }
+    private static MethodHandle findMethod(String name, boolean isStatic) {
+        MethodHandle mh = null;
+        for (Method m : CLASS.getDeclaredMethods()) {
+            if (m.getName().equals(name) &&
+                Modifier.isStatic(m.getModifiers()) == isStatic) {
+                if (mh != null)
+                    throw new RuntimeException("duplicate methods: "+name);
+                try {
+                    mh = LOOKUP.unreflect(m);
+                } catch (ReflectiveOperationException ex) {
+                    throw new RuntimeException(ex);
+                }
+            }
+        }
+        if (mh == null)
+            throw new RuntimeException("no method: "+name);
+        return mh;
+    }
+
+    int testWMTCallee;
+    private int testWMTCallee(String x) {
+        return testWMTCallee++;
+    }
+    private static MethodHandle testWMTCallee() {
+        MethodHandle callee = findVirtual("testWMTCallee");
+        // FIXME: should not have to retype callee
+        callee = callee.asType(callee.type().changeParameterType(0, Object.class));
+        return callee;
+    }
+
+    private Exception testWMT(MethodHandle[] mhs, int reps) throws Throwable {
+        testCases += 1;
+        testWMTCallee = 0;
+        int catches = 0;
+        Exception savedEx = null;
+        for (int i = 0; i < reps; i++) {
+            MethodHandle mh = mhs[i % mhs.length];
+            int n;
+            try {
+                // FIXME: should not have to retype this
+                n = (int) mh.invokeExact((Object)this, "x");
+                assertEquals(n, i - catches);
+                // Using the exact type for this causes endless deopt due to
+                // 'non_cached_result' in SystemDictionary::find_method_handle_invoke.
+                // The problem is that the compiler thread needs to access a cached
+                // invoke method, but invoke methods are not cached if one of the
+                // component types is not on the BCP.
+            } catch (Exception ex) {
+                savedEx = ex;
+                catches++;
+            }
+        }
+        //VERBOSE: System.out.println("reps="+reps+" catches="+catches);
+        return savedEx;
+    }
+
+    private static final int REPEAT = Integer.getInteger(CLASS.getSimpleName()+".REPEAT", 10);
+
+    private Exception testWMT(MethodHandle mh, MethodHandle mh1, int reps) throws Throwable {
+        //VERBOSE: System.out.println("mh="+mh+" mh1="+mh1);
+        MethodHandle[] mhs = new MethodHandle[100];
+        Arrays.fill(mhs, mh);
+        int patch = mhs.length-1;
+        Exception savedEx = null;
+        for (int i = 0; i < REPEAT; i++) {
+            mhs[patch] = mh;
+            testWMT(mhs, 10000);
+            mhs[patch] = mh1;
+            savedEx = testWMT(mhs, reps);
+        }
+        return savedEx;
+    }
+
+   private static void assertEquals(Object x, Object y) {
+        if (x == y || x != null && x.equals(y))  return;
+        throw new RuntimeException(x+" != "+y);
+    }
+}