Import MethodHandles.permuteArguments() tests from OpenJDK 11.0.13
Fix issue in exposed PermuteArguments transformer.
Bug: 207844518
Test: atest CtsLibcoreOjTestCases:test.java.lang.invoke
Test: atest CtsLibcoreTestCases:libcore.java.lang.invoke
Change-Id: I2f490719b5bc14abc42038954f887de291c696b2
diff --git a/ojluni/src/main/java/java/lang/invoke/Transformers.java b/ojluni/src/main/java/java/lang/invoke/Transformers.java
index 0751cfe..79ebc6e 100644
--- a/ojluni/src/main/java/java/lang/invoke/Transformers.java
+++ b/ojluni/src/main/java/java/lang/invoke/Transformers.java
@@ -583,8 +583,8 @@
// the permutation. We first iterate through the incoming stack frame and box
// each argument. We then unbox and write out the argument to the target frame
// according to the specified reordering.
- Object[] arguments = new Object[reorder.length];
final Class<?>[] ptypes = type().ptypes();
+ Object[] arguments = new Object[ptypes.length];
for (int i = 0; i < ptypes.length; ++i) {
final Class<?> ptype = ptypes[i];
switch (Wrapper.basicTypeChar(ptype)) {
@@ -624,7 +624,7 @@
final StackFrameWriter writer = new StackFrameWriter();
writer.attach(calleeFrame);
- for (int i = 0; i < ptypes.length; ++i) {
+ for (int i = 0; i < reorder.length; ++i) {
int idx = reorder[i];
final Class<?> ptype = ptypes[idx];
final Object argument = arguments[idx];
diff --git a/ojluni/src/test/java/lang/invoke/MethodHandlesPermuteArgumentsTest.java b/ojluni/src/test/java/lang/invoke/MethodHandlesPermuteArgumentsTest.java
new file mode 100644
index 0000000..845b68c
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/MethodHandlesPermuteArgumentsTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 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.
+ */
+
+/* @test
+ * @summary unit tests for java.lang.invoke.MethodHandles
+ * @library /lib/testlibrary /java/lang/invoke/common
+ * @compile MethodHandlesTest.java MethodHandlesPermuteArgumentsTest.java remote/RemoteExample.java
+ * @run junit/othervm/timeout=2500 -XX:+IgnoreUnrecognizedVMOptions
+ * -XX:-VerifyDependencies
+ * -esa
+ * test.java.lang.invoke.MethodHandlesPermuteArgumentsTest
+ */
+
+
+package test.java.lang.invoke;
+
+import org.junit.*;
+import test.java.lang.invoke.lib.CodeCacheOverflowProcessor;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.Arrays;
+
+import static org.junit.Assert.*;
+
+public class MethodHandlesPermuteArgumentsTest extends MethodHandlesTest {
+
+ @Test // SLOW
+ public void testPermuteArguments() throws Throwable {
+ CodeCacheOverflowProcessor.runMHTest(this::testPermuteArguments0);
+ }
+
+ // Android-added: @Test for test cases.
+ @Test
+ public void testPermuteArguments0() 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);
+ }
+
+ // Android-added: @Ignore as this is a test helper.
+ @Ignore
+ public void testPermuteArguments(int max, Class<?> type1, int t2c, Class<?> type2, int dilution) throws Throwable {
+ if (verbosity >= 2)
+ System.out.println("permuteArguments "+max+"*"+type1.getName()
+ +(t2c==0?"":"/"+t2c+"*"+type2.getName())
+ +(dilution > 0 ? " with dilution "+dilution : ""));
+ int t2pos = t2c == 0 ? 0 : 1;
+ for (int inargs = t2pos+1; inargs <= max; inargs++) {
+ Class<?>[] types = new Class<?>[inargs];
+ Arrays.fill(types, type1);
+ if (t2c != 0) {
+ // Fill in a middle range with type2:
+ Arrays.fill(types, t2pos, Math.min(t2pos+t2c, inargs), type2);
+ }
+ Object[] args = randomArgs(types);
+ int numcases = 1;
+ for (int outargs = 0; outargs <= max; outargs++) {
+ if (outargs - inargs >= MAX_ARG_INCREASE) continue;
+ int casStep = dilution + 1;
+ // Avoid some common factors:
+ while ((casStep > 2 && casStep % 2 == 0 && inargs % 2 == 0) ||
+ (casStep > 3 && casStep % 3 == 0 && inargs % 3 == 0))
+ 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.
+ for (int i = 0; i < inargs; i++) {
+ Arrays.fill(reorder, i);
+ testPermuteArguments(args, types, reorder);
+ for (int d = 1; d <= 2; d++) {
+ if (i + d >= inargs) continue;
+ for (int j = 1; j < outargs; j += 2)
+ reorder[j] += 1;
+ testPermuteArguments(args, types, reorder);
+ testPermuteArguments(args, types, reverse(reorder));
+ }
+ }
+ // Repetition of a sequence of 3 or more arguments.
+ for (int i = 1; i < inargs; i++) {
+ for (int len = 3; len <= inargs; len++) {
+ for (int j = 0; j < outargs; j++)
+ reorder[j] = (i + (j % len)) % inargs;
+ testPermuteArguments(args, types, reorder);
+ testPermuteArguments(args, types, reverse(reorder));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Android-added: @Ignore for test helper.
+ @Ignore
+ public void testPermuteArguments(Object[] args, Class<?>[] types,
+ int outargs, int numcases, int casStep) throws Throwable {
+ int inargs = args.length;
+ int[] reorder = new int[outargs];
+ for (int cas = 0; cas < numcases; cas += casStep) {
+ for (int i = 0, c = cas; i < outargs; i++) {
+ 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);
+ }
+ }
+
+ static int[] reverse(int[] reorder) {
+ reorder = reorder.clone();
+ for (int i = 0, imax = reorder.length / 2; i < imax; i++) {
+ int j = reorder.length - 1 - i;
+ int tem = reorder[i];
+ reorder[i] = reorder[j];
+ reorder[j] = tem;
+ }
+ return reorder;
+ }
+
+ // Android-added: @Ignore for test helper.
+ @Ignore
+ void testPermuteArguments(Object[] args, Class<?>[] types, int[] reorder) throws Throwable {
+ countTest();
+ if (args == null && types == null) {
+ int max = 0;
+ for (int j : reorder) {
+ if (max < j) max = j;
+ }
+ args = randomArgs(max+1, Integer.class);
+ }
+ if (args == null) {
+ args = randomArgs(types);
+ }
+ if (types == null) {
+ types = new Class<?>[args.length];
+ for (int i = 0; i < args.length; i++)
+ types[i] = args[i].getClass();
+ }
+ int inargs = args.length, outargs = reorder.length;
+ assertTrue(inargs == types.length);
+ if (verbosity >= 3)
+ System.out.println("permuteArguments "+Arrays.toString(reorder));
+ Object[] permArgs = new Object[outargs];
+ Class<?>[] permTypes = new Class<?>[outargs];
+ for (int i = 0; i < outargs; i++) {
+ permArgs[i] = args[reorder[i]];
+ permTypes[i] = types[reorder[i]];
+ }
+ if (verbosity >= 4) {
+ System.out.println("in args: "+Arrays.asList(args));
+ System.out.println("out args: "+Arrays.asList(permArgs));
+ System.out.println("in types: "+Arrays.asList(types));
+ System.out.println("out types: "+Arrays.asList(permTypes));
+ }
+ MethodType inType = MethodType.methodType(Object.class, types);
+ MethodType outType = MethodType.methodType(Object.class, permTypes);
+ MethodHandle target = varargsList(outargs).asType(outType);
+ MethodHandle newTarget = MethodHandles.permuteArguments(target, inType, reorder);
+ if (verbosity >= 5) System.out.println("newTarget = "+newTarget);
+ Object result = newTarget.invokeWithArguments(args);
+ Object expected = Arrays.asList(permArgs);
+ if (!expected.equals(result)) {
+ System.out.println("*** failed permuteArguments "+Arrays.toString(reorder)+
+ " types="+Arrays.asList(types));
+ System.out.println("in args: "+Arrays.asList(args));
+ System.out.println("out args: "+expected);
+ System.out.println("bad args: "+result);
+ }
+ assertEquals(expected, result);
+ }
+}
diff --git a/ojluni/src/test/java/lang/invoke/PermuteArgsReturnVoidTest.java b/ojluni/src/test/java/lang/invoke/PermuteArgsReturnVoidTest.java
new file mode 100644
index 0000000..b01a4aa
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/PermuteArgsReturnVoidTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 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.
+ */
+
+/* @test
+ * @bug 8184119
+ * @summary test permutation when return value is directly derived from an argument
+ * @run testng/othervm test.java.lang.invoke.PermuteArgsReturnVoidTest
+ */
+
+
+package test.java.lang.invoke;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+import static java.lang.invoke.MethodHandles.dropArguments;
+import static java.lang.invoke.MethodHandles.identity;
+
+public class PermuteArgsReturnVoidTest {
+
+ static String consumeIdentity(String s, int i1, int i2) {
+ return s;
+ }
+
+ static void consumeVoid(String s, int i1, int i2) {
+ }
+
+ @Test
+ public void testReturnOnStack() throws Throwable {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+
+ MethodHandle consumeIdentity = l.findStatic(
+ PermuteArgsReturnVoidTest.class, "consumeIdentity",
+ MethodType.methodType(String.class, String.class, int.class, int.class));
+ MethodHandle consumeVoid = l.findStatic(
+ PermuteArgsReturnVoidTest.class, "consumeVoid",
+ MethodType.methodType(void.class, String.class, int.class, int.class));
+
+ MethodHandle f = MethodHandles.foldArguments(consumeIdentity, consumeVoid);
+
+ MethodHandle p = MethodHandles.permuteArguments(f, MethodType.methodType(String.class, String.class, int.class, int.class), 0, 2, 1);
+
+ String s = (String) p.invoke("IN", 0, 0);
+ Assert.assertEquals(s.getClass(), String.class);
+ Assert.assertEquals(s, "IN");
+ }
+
+ @Test
+ public void testReturnFromArg() throws Throwable {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+
+ MethodHandle consumeIdentity = dropArguments(
+ identity(String.class), 1, int.class, int.class);
+ MethodHandle consumeVoid = l.findStatic(
+ PermuteArgsReturnVoidTest.class, "consumeVoid",
+ MethodType.methodType(void.class, String.class, int.class, int.class));
+
+ MethodHandle f = MethodHandles.foldArguments(consumeIdentity, consumeVoid);
+
+ MethodHandle p = MethodHandles.permuteArguments(f, MethodType.methodType(String.class, String.class, int.class, int.class), 0, 2, 1);
+
+ String s = (String) p.invoke("IN", 0, 0);
+ Assert.assertEquals(s.getClass(), String.class);
+ Assert.assertEquals(s, "IN");
+ }
+}
diff --git a/ojluni/src/test/java/lang/invoke/PermuteArgsTest.java b/ojluni/src/test/java/lang/invoke/PermuteArgsTest.java
new file mode 100644
index 0000000..e175102
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/PermuteArgsTest.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (c) 2011, 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.
+ */
+
+/* @test
+ * @summary unit tests for method handles which permute their arguments
+ * @library /lib/testlibrary /java/lang/invoke/common
+ * @run testng/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:-VerifyDependencies -ea -esa -DPermuteArgsTest.MAX_ARITY=8 test.java.lang.invoke.PermuteArgsTest
+ */
+
+/* Examples of manual runs:
+ * java -DPermuteArgsTest.{DRY_RUN=true,MAX_ARITY=253} test.java.lang.invoke.PermuteArgsTest
+ * java -DPermuteArgsTest.{VERBOSE=true,MAX_ARITY=5} test.java.lang.invoke.PermuteArgsTest
+ * java test.java.lang.invoke.PermuteArgsTest list3I[2,0,1] listJLJ[2,0,1]
+ */
+
+package test.java.lang.invoke;
+
+import org.testng.annotations.Test;
+import test.java.lang.invoke.lib.CodeCacheOverflowProcessor;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.WrongMethodTypeException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import static java.lang.invoke.MethodHandles.Lookup;
+import static java.lang.invoke.MethodHandles.lookup;
+import static java.lang.invoke.MethodHandles.permuteArguments;
+import static java.lang.invoke.MethodType.methodType;
+
+public class PermuteArgsTest {
+ private static final Class<?> CLASS = PermuteArgsTest.class;
+ private static final int MAX_ARITY = Integer.getInteger(CLASS.getSimpleName()+".MAX_ARITY", 8);
+ private static final boolean DRY_RUN = Boolean.getBoolean(CLASS.getSimpleName()+".DRY_RUN");
+ private static final boolean VERBOSE = Boolean.getBoolean(CLASS.getSimpleName()+".VERBOSE") || DRY_RUN;
+
+ static Object list2I(int x, int y) {
+ return Arrays.asList(x, y);
+ }
+ static Object list3I(int x, int y, int z) {
+ return Arrays.asList(x, y, z);
+ }
+ static Object list4I(int w, int x, int y, int z) {
+ return Arrays.asList(w, x, y, z);
+ }
+ static Object list2J(long x, long y) {
+ return Arrays.asList(x, y);
+ }
+ static Object list3J(long x, long y, long z) {
+ return Arrays.asList(x, y, z);
+ }
+ static Object list4J(long w, long x, long y, long z) {
+ return Arrays.asList(w, x, y, z);
+ }
+ static Object list2I2J(int w, int x, long y, long z) {
+ return Arrays.asList(w, x, y, z);
+ }
+ static Object list2J2I(long w, long x, int y, int z) {
+ return Arrays.asList(w, x, y, z);
+ }
+ static Object listLJJ(Object x, long y, long z) {
+ return Arrays.asList(x, y, z);
+ }
+ static Object listJLJ(long x, Object y, long z) {
+ return Arrays.asList(x, y, z);
+ }
+ static Object listJJL(long x, long y, Object z) {
+ return Arrays.asList(x, y, z);
+ }
+ static Object listJLL(long x, Object y, Object z) {
+ return Arrays.asList(x, y, z);
+ }
+ static Object listLJL(Object x, long y, Object z) {
+ return Arrays.asList(x, y, z);
+ }
+ static Object listLLJ(Object x, Object y, long z) {
+ return Arrays.asList(x, y, z);
+ }
+ static Object listJLLJ(long w, Object x, Object y, long z) {
+ return Arrays.asList(w, x, y, z);
+ }
+ static Object listLJJL(Object w, long x, long y, Object z) {
+ return Arrays.asList(w, x, y, z);
+ }
+ static Object listI_etc(int... va) {
+ ArrayList<Object> res = new ArrayList<>();
+ for (int x : va) res.add(x);
+ return res;
+ }
+ static Object listIJL_etc(int x, long y, Object z, Object... va) {
+ ArrayList<Object> res = new ArrayList<>();
+ res.addAll(Arrays.asList(x, y, z));
+ res.addAll(Arrays.asList(va));
+ return res;
+ }
+
+ public static void main(String argv[]) throws Throwable {
+ if (argv.length > 0) {
+ for (String arg : argv) {
+ // arg ::= name[n,...]
+ int k = arg.indexOf('[');
+ String mhName = arg.substring(0, k).trim();
+ String permString = arg.substring(k);
+ testOnePermutation(mhName, permString);
+ }
+ return;
+ }
+ new PermuteArgsTest().test();
+ }
+
+ static int testCases;
+
+ @Test
+ public void test() throws Throwable {
+ CodeCacheOverflowProcessor.runMHTest(this::test0);
+ }
+
+ public void test0() throws Throwable {
+ testCases = 0;
+ Lookup lookup = lookup();
+ for (Method m : lookup.lookupClass().getDeclaredMethods()) {
+ if (m.getName().startsWith("list") &&
+ Modifier.isStatic(m.getModifiers())) {
+ test(m.getName(), lookup.unreflect(m));
+ }
+ }
+ System.out.println("ran a total of "+testCases+" test cases");
+ }
+
+ static int jump(int i, int min, int max) {
+ if (i >= min && i <= max-1) {
+ // jump faster
+ int len = max-min;
+ if (i < min + len/2)
+ i = min + len/2;
+ else
+ i = max-1;
+ }
+ return i;
+ }
+
+ static void test(String name, MethodHandle mh) throws Throwable {
+ if (VERBOSE)
+ System.out.println("mh = "+name+" : "+mh+" { "
+ +Arrays.toString(junkArgs(mh.type().parameterArray())));
+ int testCases0 = testCases;
+ if (!mh.isVarargsCollector()) {
+ // normal case
+ testPermutations(mh);
+ } else {
+ // varargs case; add params up to MAX_ARITY
+ MethodType mt = mh.type();
+ int posArgs = mt.parameterCount() - 1;
+ int arity0 = Math.max(3, posArgs);
+ for (int arity = arity0; arity <= MAX_ARITY; arity++) {
+ MethodHandle mh1;
+ try {
+ mh1 = adjustArity(mh, arity);
+ } catch (IllegalArgumentException ex) {
+ System.out.println("*** mh = "+name+" : "+mh+"; arity = "+arity+" => "+ex);
+ ex.printStackTrace(System.out);
+ break; // cannot get this arity for this type
+ }
+ test("("+arity+")"+name, mh1);
+ arity = jump(arity, arity0*2, MAX_ARITY);
+ }
+ }
+ if (VERBOSE)
+ System.out.println("ran "+(testCases - testCases0)+" test cases for "+name+" }");
+ }
+
+ static MethodHandle adjustArity(MethodHandle mh, int arity) {
+ MethodType mt = mh.type();
+ int posArgs = mt.parameterCount() - 1;
+ Class<?> reptype = mt.parameterType(posArgs).getComponentType();
+ MethodType mt1 = mt.dropParameterTypes(posArgs, posArgs+1);
+ while (mt1.parameterCount() < arity) {
+ Class<?> pt = reptype;
+ if (pt == Object.class && posArgs > 0)
+ // repeat types cyclically if possible:
+ pt = mt1.parameterType(mt1.parameterCount() - posArgs);
+ mt1 = mt1.appendParameterTypes(pt);
+ }
+ try {
+ return mh.asType(mt1);
+ } catch (WrongMethodTypeException | IllegalArgumentException ex) {
+ throw new IllegalArgumentException("cannot convert to type "+mt1+" from "+mh, ex);
+ }
+ }
+ static MethodHandle findTestMH(String name, int[] perm)
+ throws ReflectiveOperationException {
+ int arity = perm.length;
+ Lookup lookup = lookup();
+ for (Method m : lookup.lookupClass().getDeclaredMethods()) {
+ if (m.getName().equals(name) &&
+ Modifier.isStatic(m.getModifiers())) {
+ MethodHandle mh = lookup.unreflect(m);
+ int mhArity = mh.type().parameterCount();
+ if (mh.isVarargsCollector()) {
+ if (mhArity-1 <= arity)
+ return adjustArity(mh, arity);
+ } else if (mhArity == arity) {
+ return mh;
+ }
+ }
+ }
+ throw new RuntimeException("no such method for arity "+arity+": "+name);
+ }
+
+ static void testPermutations(MethodHandle mh) throws Throwable {
+ HashSet<String> done = new HashSet<>();
+ MethodType mt = mh.type();
+ int[] perm = nullPerm(mt.parameterCount());
+ final int MARGIN = (perm.length <= 10 ? 2 : 0);
+ int testCases0 = testCases;
+ for (int j = 0; j <= 1; j++) {
+ int maxStart = perm.length-1;
+ if (j != 0) maxStart /= 2;
+ for (int start = 0; start <= maxStart; start++) {
+ int maxOmit = (maxStart - start) / 2;
+ if (start != 0) maxOmit = 2;
+ if (j != 0) maxOmit = 1;
+ for (int omit = 0; omit <= maxOmit; omit++) {
+ int end = perm.length - omit;
+ if (end - start >= 2) {
+ //System.out.println("testPermutations"+Arrays.asList(start, end)+(j == 0 ? "" : " (reverse)"));
+ testPermutations(mh, perm, start, end, done);
+ }
+ omit = jump(omit, (start == 0 && j == 0 ? MARGIN : 0), maxOmit);
+ }
+ start = jump(start, (j == 0 ? MARGIN : 0), maxStart);
+ }
+ // do everything in reverse:
+ reverse(perm, 0, perm.length);
+ }
+ switch (perm.length) {
+ case 2: assert(testCases - testCases0 == 2); break;
+ case 3: assert(testCases - testCases0 == 6); break;
+ case 4: assert(testCases - testCases0 == 24); break;
+ case 5: assert(testCases - testCases0 == 120); break;
+ case 6: assert(testCases - testCases0 > 720/3); break;
+ }
+ }
+
+ static void testPermutations(MethodHandle mh, int[] perm, int start, int end,
+ Set<String> done) throws Throwable {
+ if (end - start <= 1) return;
+ for (int j = 0; j <= 1; j++) {
+ testRotations(mh, perm, start, end, done);
+ if (end - start <= 2) return;
+ reverse(perm, start, end);
+ }
+ if (end - start <= 3) return;
+ int excess4 = (end - start) - 4;
+ // composed rotations:
+ int start2 = start + 1 + excess4/3;
+ int end2 = end - excess4/3;
+ end2 = start2 + Math.min(start == 0 ? 4 : 3, end2 - start2);
+ int skips = (perm.length+3)/5;
+ for (int i = start; i < end; i++) {
+ rotate(perm, start, end);
+ if (skips > 1 && ((i-start) + (i-start)/7) % skips != 0) continue;
+ for (int j = 0; j <= 1; j++) {
+ testPermutations(mh, perm, start2, end2, done);
+ reverse(perm, start, end);
+ }
+ }
+ }
+
+ static void testRotations(MethodHandle mh, int[] perm, int start, int end,
+ Set<String> done) throws Throwable {
+ Object[] args = junkArgs(mh.type().parameterArray());
+ for (int i = start; i < end; i++) {
+ if (done.add(Arrays.toString(perm)))
+ testOnePermutation(mh, perm, args);
+ rotate(perm, start, end);
+ }
+ }
+
+ static void testOnePermutation(MethodHandle mh, int[] perm, Object[] args)
+ throws Throwable {
+ MethodType mt = mh.type();
+ MethodType pmt = methodType(mt.returnType(),
+ unpermuteArgs(perm, mt.parameterArray(), Class[].class));
+ if (VERBOSE)
+ System.out.println(Arrays.toString(perm));
+ testCases += 1;
+ if (DRY_RUN)
+ return;
+ Object res = permuteArguments(mh, pmt, perm).invokeWithArguments(unpermuteArgs(perm, args));
+ String str = String.valueOf(res);
+ if (!Arrays.toString(args).equals(str)) {
+ System.out.println(Arrays.toString(perm)+" "+str+" *** WRONG ***");
+ }
+ }
+
+ // For reproducing failures:
+ static void testOnePermutation(String mhName, String permString) throws Throwable {
+ String s = permString;
+ s = s.replace('[', ' ').replace(']', ' ').replace(',', ' '); // easier to trim spaces
+ s = s.trim();
+ int[] perm = new int[s.length()];
+ int arity = 0;
+ while (!s.isEmpty()) {
+ int k = s.indexOf(' ');
+ if (k < 0) k = s.length();
+ perm[arity++] = Integer.parseInt(s.substring(0, k));
+ s = s.substring(k).trim();
+ }
+ perm = Arrays.copyOf(perm, arity);
+ testOnePermutation(mhName, perm);
+ }
+ static void testOnePermutation(String mhName, int[] perm) throws Throwable {
+ MethodHandle mh = findTestMH(mhName, perm);
+ System.out.println("mh = "+mhName+" : "+mh+" { "
+ +Arrays.toString(junkArgs(mh.type().parameterArray())));
+ Object[] args = junkArgs(mh.type().parameterArray());
+ testOnePermutation(mh, perm, args);
+ System.out.println("}");
+ }
+
+ static Object[] junkArgs(Class<?>[] ptypes) {
+ Object[] args = new Object[ptypes.length];
+ for (int i = 0; i < ptypes.length; i++) {
+ Class<?> pt = ptypes[i];
+ Object arg;
+ if (pt == Void.class) arg = null;
+ else if (pt == int.class) arg = i + 101;
+ else if (pt == long.class) arg = i + 10_000_000_001L;
+ else arg = "#" + (i + 1);
+ args[i] = arg;
+ }
+ return args;
+ }
+
+ static int[] nullPerm(int len) {
+ int[] perm = new int[len];
+ for (int i = 0; i < len; i++)
+ perm[i] = i;
+ return perm;
+ }
+ static void rotate(int[] perm) {
+ rotate(perm, 0, perm.length);
+ }
+ static void rotate(int[] perm, int start, int end) {
+ int x = perm[end-1];
+ for (int j = start; j < end; j++) {
+ int y = perm[j]; perm[j] = x; x = y;
+ }
+ }
+ static void reverse(int[] perm) {
+ reverse(perm, 0, perm.length);
+ }
+ static void reverse(int[] perm, int start, int end) {
+ int mid = start + (end - start)/2;
+ for (int j = start; j < mid; j++) {
+ int k = (end-1) - j;
+ int x = perm[j]; perm[j] = perm[k]; perm[k] = x;
+ }
+ }
+ // Permute the args according to the inverse of perm.
+ static Object[] unpermuteArgs(int[] perm, Object[] args) {
+ return unpermuteArgs(perm, args, Object[].class);
+ }
+ static <T> T[] unpermuteArgs(int[] perm, T[] args, Class<T[]> Tclass) {
+ T[] res = Arrays.copyOf(new Object[0], perm.length, Tclass);
+ for (int i = 0; i < perm.length; i++)
+ res[perm[i]] = args[i];
+ return res;
+ }
+}