8019417: JSR 292 javadoc should clarify method handle arity limits
authorjrose
Sat, 05 Oct 2013 05:30:39 -0700
changeset 20529 b49b07206f7d
parent 20528 0b1e2130d3f7
child 20530 b54a1f5cd35f
8019417: JSR 292 javadoc should clarify method handle arity limits Summary: clarification of erroneous reading of spec. that led to 7194534 Reviewed-by: twisti, darcy
jdk/src/share/classes/java/lang/invoke/MethodHandle.java
jdk/src/share/classes/java/lang/invoke/MethodHandles.java
jdk/test/java/lang/invoke/BigArityTest.java
--- a/jdk/src/share/classes/java/lang/invoke/MethodHandle.java	Sat Oct 05 05:30:38 2013 -0700
+++ b/jdk/src/share/classes/java/lang/invoke/MethodHandle.java	Sat Oct 05 05:30:39 2013 -0700
@@ -392,7 +392,7 @@
  * Java types.
  * <ul>
  * <li>Method types range over all possible arities,
- * from no arguments to up to 255 of arguments (a limit imposed by the JVM).
+ * from no arguments to up to the  <a href="MethodHandle.html#maxarity">maximum number</a> of allowed arguments.
  * Generics are not variadic, and so cannot represent this.</li>
  * <li>Method types can specify arguments of primitive types,
  * which Java generic types cannot range over.</li>
@@ -402,6 +402,22 @@
  * genericity with a Java type parameter.</li>
  * </ul>
  *
+ * <h1><a name="maxarity"></a>Arity limits</h1>
+ * The JVM imposes on all methods and constructors of any kind an absolute
+ * limit of 255 stacked arguments.  This limit can appear more restrictive
+ * in certain cases:
+ * <ul>
+ * <li>A {@code long} or {@code double} argument counts (for purposes of arity limits) as two argument slots.
+ * <li>A non-static method consumes an extra argument for the object on which the method is called.
+ * <li>A constructor consumes an extra argument for the object which is being constructed.
+ * <li>Since a method handle&rsquo;s {@code invoke} method (or other signature-polymorphic method) is non-virtual,
+ *     it consumes an extra argument for the method handle itself, in addition to any non-virtual receiver object.
+ * </ul>
+ * These limits imply that certain method handles cannot be created, solely because of the JVM limit on stacked arguments.
+ * For example, if a static JVM method accepts exactly 255 arguments, a method handle cannot be created for it.
+ * Attempts to create method handles with impossible method types lead to an {@link IllegalArgumentException}.
+ * In particular, a method handle&rsquo;s type must not have an arity of the exact maximum 255.
+ *
  * @see MethodType
  * @see MethodHandles
  * @author John Rose, JSR 292 EG
@@ -831,10 +847,12 @@
      * @return a new method handle which spreads its final array argument,
      *         before calling the original method handle
      * @throws NullPointerException if {@code arrayType} is a null reference
-     * @throws IllegalArgumentException if {@code arrayType} is not an array type
-     * @throws IllegalArgumentException if target does not have at least
+     * @throws IllegalArgumentException if {@code arrayType} is not an array type,
+     *         or if target does not have at least
      *         {@code arrayLength} parameter types,
-     *         or if {@code arrayLength} is negative
+     *         or if {@code arrayLength} is negative,
+     *         or if the resulting method handle's type would have
+     *         <a href="MethodHandle.html#maxarity">too many parameters</a>
      * @throws WrongMethodTypeException if the implied {@code asType} call fails
      * @see #asCollector
      */
@@ -947,7 +965,9 @@
      * @throws NullPointerException if {@code arrayType} is a null reference
      * @throws IllegalArgumentException if {@code arrayType} is not an array type
      *         or {@code arrayType} is not assignable to this method handle's trailing parameter type,
-     *         or {@code arrayLength} is not a legal array size
+     *         or {@code arrayLength} is not a legal array size,
+     *         or the resulting method handle's type would have
+     *         <a href="MethodHandle.html#maxarity">too many parameters</a>
      * @throws WrongMethodTypeException if the implied {@code asType} call fails
      * @see #asSpreader
      * @see #asVarargsCollector
--- a/jdk/src/share/classes/java/lang/invoke/MethodHandles.java	Sat Oct 05 05:30:38 2013 -0700
+++ b/jdk/src/share/classes/java/lang/invoke/MethodHandles.java	Sat Oct 05 05:30:39 2013 -0700
@@ -245,6 +245,10 @@
      * on various grounds (<a href="#secmgr">see below</a>).
      * By contrast, the {@code ldc} instruction is not subject to
      * security manager checks.
+     * <li>If the looked-up method has a
+     * <a href="MethodHandle.html#maxarity">very large arity</a>,
+     * the method handle creation may fail, due to the method handle
+     * type having too many parameters.
      * </ul>
      *
      * <h1><a name="access"></a>Access checking</h1>
@@ -1522,7 +1526,9 @@
      * @return a method handle suitable for invoking any method handle of the given type
      * @throws NullPointerException if {@code type} is null
      * @throws IllegalArgumentException if {@code leadingArgCount} is not in
-     *                  the range from 0 to {@code type.parameterCount()} inclusive
+     *                  the range from 0 to {@code type.parameterCount()} inclusive,
+     *                  or if the resulting method handle's type would have
+     *          <a href="MethodHandle.html#maxarity">too many parameters</a>
      */
     static public
     MethodHandle spreadInvoker(MethodType type, int leadingArgCount) {
@@ -1565,6 +1571,8 @@
      * This method throws no reflective or security exceptions.
      * @param type the desired target type
      * @return a method handle suitable for invoking any method handle of the given type
+     * @throws IllegalArgumentException if the resulting method handle's type would have
+     *          <a href="MethodHandle.html#maxarity">too many parameters</a>
      */
     static public
     MethodHandle exactInvoker(MethodType type) {
@@ -1598,6 +1606,8 @@
      * This method throws no reflective or security exceptions.
      * @param type the desired target type
      * @return a method handle suitable for invoking any method handle convertible to the given type
+     * @throws IllegalArgumentException if the resulting method handle's type would have
+     *          <a href="MethodHandle.html#maxarity">too many parameters</a>
      */
     static public
     MethodHandle invoker(MethodType type) {
@@ -1966,7 +1976,8 @@
      *                              or if the {@code valueTypes} array or any of its elements is null
      * @throws IllegalArgumentException if any element of {@code valueTypes} is {@code void.class},
      *                  or if {@code pos} is negative or greater than the arity of the target,
-     *                  or if the new method handle's type would have too many parameters
+     *                  or if the new method handle's type would have
+     *                  <a href="MethodHandle.html#maxarity">too many parameters</a>
      */
     public static
     MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes) {
@@ -2034,7 +2045,9 @@
      *                              or if the {@code filters} array is null
      * @throws IllegalArgumentException if a non-null element of {@code filters}
      *          does not match a corresponding argument type of target as described above,
-     *          or if the {@code pos+filters.length} is greater than {@code target.type().parameterCount()}
+     *          or if the {@code pos+filters.length} is greater than {@code target.type().parameterCount()},
+     *          or if the resulting method handle's type would have
+     *          <a href="MethodHandle.html#maxarity">too many parameters</a>
      */
     public static
     MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters) {
--- a/jdk/test/java/lang/invoke/BigArityTest.java	Sat Oct 05 05:30:38 2013 -0700
+++ b/jdk/test/java/lang/invoke/BigArityTest.java	Sat Oct 05 05:30:39 2013 -0700
@@ -93,6 +93,65 @@
     }
 
     @Test
+    public void asCollectorIAE01() throws ReflectiveOperationException {
+        final int [] INVALID_ARRAY_LENGTHS = {
+            Integer.MIN_VALUE, Integer.MIN_VALUE + 1, -2, -1, 255, 256, Integer.MAX_VALUE - 1, Integer.MAX_VALUE
+        };
+        MethodHandle target = MethodHandles.publicLookup().findStatic(Arrays.class,
+                "deepToString", MethodType.methodType(String.class, Object[].class));
+        int minbig = Integer.MAX_VALUE;
+        for (int invalidLength : INVALID_ARRAY_LENGTHS) {
+            if (minbig > invalidLength && invalidLength > 100)  minbig = invalidLength;
+            try {
+                target.asCollector(Object[].class, invalidLength);
+                assert(false) : invalidLength;
+            } catch (IllegalArgumentException ex) {
+                System.out.println("OK: "+ex);
+            }
+        }
+        // Sizes not in the above array are good:
+        target.asCollector(Object[].class, minbig-1);
+        for (int i = 2; i <= 10; i++)
+            target.asCollector(Object[].class, minbig-i);
+    }
+
+    @Test
+    public void invoker02() {
+        for (int i = 0; i < 255; i++) {
+            MethodType mt = MethodType.genericMethodType(i);
+            MethodType expMT = mt.insertParameterTypes(0, MethodHandle.class);
+            if (i < 254) {
+                assertEquals(expMT, MethodHandles.invoker(mt).type());
+            } else {
+                try {
+                    MethodHandles.invoker(mt);
+                    assert(false) : i;
+                } catch (IllegalArgumentException ex) {
+                    System.out.println("OK: "+ex);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void exactInvoker02() {
+        for (int i = 0; i < 255; i++) {
+            MethodType mt = MethodType.genericMethodType(i);
+            MethodType expMT = mt.insertParameterTypes(0, MethodHandle.class);
+            if (i < 254) {
+                assertEquals(expMT, MethodHandles.exactInvoker(mt).type());
+            } else {
+                try {
+                    MethodHandles.exactInvoker(mt);
+                    assert(false) : i;
+                } catch (IllegalArgumentException ex) {
+                    System.out.println("OK: "+ex);
+                }
+            }
+        }
+    }
+
+    @Test
     public void testBoundaryValues() throws Throwable {
         for (int badArity : new int[]{ -1, MAX_JVM_ARITY+1, MAX_JVM_ARITY }) {
             try {
@@ -102,6 +161,37 @@
                 System.out.println("OK: "+ex);
             }
         }
+        final int MAX_MH_ARITY      = MAX_JVM_ARITY - 1;  // mh.invoke(arg*[N])
+        final int MAX_INVOKER_ARITY = MAX_MH_ARITY - 1;   // inv.invoke(mh, arg*[N])
+        for (int arity : new int[]{ 0, 1, MAX_MH_ARITY-2, MAX_MH_ARITY-1, MAX_MH_ARITY }) {
+            MethodHandle mh = MH_hashArguments(arity);
+            if (arity < MAX_INVOKER_ARITY) {
+                MethodHandle ximh = MethodHandles.exactInvoker(mh.type());
+                MethodHandle gimh = MethodHandles.invoker(mh.type());
+                MethodHandle simh = MethodHandles.spreadInvoker(mh.type(), 0);
+                if (arity != 0) {
+                    simh = MethodHandles.spreadInvoker(mh.type(), 1);
+                } else {
+                    try {
+                        simh = MethodHandles.spreadInvoker(mh.type(), 1);
+                        assert(false) : arity;
+                    } catch (IllegalArgumentException ex) {
+                        System.out.println("OK: "+ex);
+                    }
+                }
+                if (arity != 0) {
+                    simh = MethodHandles.spreadInvoker(mh.type(), arity-1);
+                } else {
+                    try {
+                        simh = MethodHandles.spreadInvoker(mh.type(), arity-1);
+                        assert(false) : arity;
+                    } catch (IllegalArgumentException ex) {
+                        System.out.println("OK: "+ex);
+                    }
+                }
+                simh = MethodHandles.spreadInvoker(mh.type(), arity);
+            }
+        }
     }
 
     // Make sure the basic argument spreading and varargs mechanisms are working.
@@ -133,7 +223,7 @@
             if (cls == Object[].class)
                 r = smh.invokeExact(tail);
             else if (cls == Integer[].class)
-                r = smh.invokeExact((Integer[]) tail);
+                r = smh.invokeExact((Integer[]) tail); //warning OK, see 8019340
             else
                 r = smh.invoke(tail);
             assertEquals(r0, r);
@@ -235,21 +325,41 @@
             MethodHandle mh_VA = mh.asSpreader(cls, arity);
             assert(mh_VA.type().parameterType(0) == cls);
             testArities(cls, arity, iterations, verbose, mh, mh_VA);
+            // mh_CA will collect arguments of a particular type and pass them to mh_VA
+            MethodHandle mh_CA = mh_VA.asCollector(cls, arity);
+            MethodHandle mh_VA2 = mh_CA.asSpreader(cls, arity);
+            assert(mh_CA.type().equals(mh.type()));
+            assert(mh_VA2.type().equals(mh_VA.type()));
             if (cls != Object[].class) {
-                // mh_CA will collect arguments of a particular type and pass them to mh_VA
-                MethodHandle mh_CA = mh_VA.asCollector(cls, arity);
-                MethodHandle mh_VA2 = mh_CA.asSpreader(cls, arity);
                 try {
                     mh_VA2.invokeWithArguments(new Object[arity]);
                     throw new AssertionError("should not reach");
                 } catch (ClassCastException | WrongMethodTypeException ex) {
                 }
-                assert(mh_CA.type().equals(mh.type()));
-                assert(mh_VA2.type().equals(mh_VA.type()));
-                testArities(cls, arity, iterations, false, mh_CA, mh_VA2);
             }
+            int iterations_VA = iterations / 100;
+            testArities(cls, arity, iterations_VA, false, mh_CA, mh_VA2);
         }
     }
+
+   /**
+     * Tests calls to {@link BigArityTest#hashArguments hashArguments} as related to a single given arity N.
+     * Applies the given {@code mh} to a set of N integer arguments, checking the answer.
+     * Also applies the varargs variation {@code mh_VA} to an array of type C[] (given by {@code cls}).
+     * Test steps:
+     * <ul>
+     * <li>mh_VA.invokeExact(new C[]{ arg, ... })</li>
+     * <li>mh.invokeWithArguments((Object[]) new C[]{ arg, ... })</li>
+     * <li>exactInvoker(mh.type()).invokeWithArguments(new Object[]{ mh, arg, ... })</li>
+     * <li>invoker(mh.type()).invokeWithArguments(new Object[]{ mh, arg, ... })</li>
+     * </ul>
+     * @param cls     array type for varargs call (one of Object[], Number[], Integer[], Comparable[])
+     * @param arity   N, the number of arguments to {@code mh} and length of its varargs array, in [0..255]
+     * @param iterations  number of times to repeat each test step (at least 4)
+     * @param verbose are we printing extra output?
+     * @param mh      a fixed-arity version of {@code hashArguments}
+     * @param mh_VA   a variable-arity version of {@code hashArguments}, accepting the given array type {@code cls}
+     */
     private void testArities(Class<? extends Object[]> cls,
                              int arity,
                              int iterations,
@@ -292,7 +402,7 @@
             if (cls == Object[].class)
                 r = mh_VA.invokeExact(args);
             else if (cls == Integer[].class)
-                r = mh_VA.invokeExact((Integer[])args);
+                r = mh_VA.invokeExact((Integer[])args); //warning OK, see 8019340
             else
                 r = mh_VA.invoke(args);
             assertEquals(r0, r);
@@ -392,10 +502,16 @@
     a[0xE0], a[0xE1], a[0xE2], a[0xE3], a[0xE4], a[0xE5], a[0xE6], a[0xE7], a[0xE8], a[0xE9], a[0xEA], a[0xEB], a[0xEC], a[0xED], a[0xEE], a[0xEF],
     a[0xF0], a[0xF1], a[0xF2], a[0xF3], a[0xF4], a[0xF5], a[0xF6], a[0xF7],
     // </editor-fold>
-    a[0xF8], a[0xF9], a[0xFA], a[0xFB]);
+    a[0xF8], a[0xF9], a[0xFA], a[0xFB]); // hashArguments_252
         assertEquals(r0, r);
         MethodType mt = MethodType.genericMethodType(ARITY);
         MethodHandle mh = MethodHandles.lookup().findStatic(BigArityTest.class, "hashArguments_"+ARITY, mt);
+        test252(mh, a, r0);
+        MethodHandle mh_CA = MH_hashArguments_VA.asFixedArity().asCollector(Object[].class, ARITY);
+        test252(mh_CA, a, r0);
+    }
+    public void test252(MethodHandle mh, Object[] a, Object r0) throws Throwable {
+        Object r;
         r = mh.invokeExact(
     // <editor-fold defaultstate="collapsed" desc="a[0x00], a[0x01], a[0x02], a[0x03], a[0x04], ...">
     a[0x00], a[0x01], a[0x02], a[0x03], a[0x04], a[0x05], a[0x06], a[0x07], a[0x08], a[0x09], a[0x0A], a[0x0B], a[0x0C], a[0x0D], a[0x0E], a[0x0F],
@@ -599,10 +715,16 @@
     a[0xE0], a[0xE1], a[0xE2], a[0xE3], a[0xE4], a[0xE5], a[0xE6], a[0xE7], a[0xE8], a[0xE9], a[0xEA], a[0xEB], a[0xEC], a[0xED], a[0xEE], a[0xEF],
     a[0xF0], a[0xF1], a[0xF2], a[0xF3], a[0xF4], a[0xF5], a[0xF6], a[0xF7],
     // </editor-fold>
-    a[0xF8], a[0xF9], a[0xFA], a[0xFB], a[0xFC]);
+    a[0xF8], a[0xF9], a[0xFA], a[0xFB], a[0xFC]); // hashArguments_253
         assertEquals(r0, r);
         MethodType mt = MethodType.genericMethodType(ARITY);
         MethodHandle mh = MethodHandles.lookup().findStatic(BigArityTest.class, "hashArguments_"+ARITY, mt);
+        test253(mh, a, r0);
+        MethodHandle mh_CA = MH_hashArguments_VA.asFixedArity().asCollector(Object[].class, ARITY);
+        test253(mh_CA, a, r0);
+    }
+    public void test253(MethodHandle mh, Object[] a, Object r0) throws Throwable {
+        Object r;
         r = mh.invokeExact(
     // <editor-fold defaultstate="collapsed" desc="a[0x00], a[0x01], a[0x02], a[0x03], a[0x04], ...">
     a[0x00], a[0x01], a[0x02], a[0x03], a[0x04], a[0x05], a[0x06], a[0x07], a[0x08], a[0x09], a[0x0A], a[0x0B], a[0x0C], a[0x0D], a[0x0E], a[0x0F],
@@ -648,7 +770,6 @@
     // </editor-fold>
     a[0xF8], a[0xF9], a[0xFA], a[0xFB], a[0xFC]);
         assertEquals(r0, r);
-        // FIXME: This next one fails, because it uses an internal invoker of arity 255.
         r = ximh.invokeWithArguments(cat(mh,a));
         assertEquals(r0, r);
         MethodHandle gimh = MethodHandles.invoker(mh.type());
@@ -674,7 +795,6 @@
     // </editor-fold>
     a[0xF8], a[0xF9], a[0xFA], a[0xFB], a[0xFC]);
         assertEquals(r0, r);
-        // FIXME: This next one fails, because it uses an internal invoker of arity 255.
         r = gimh.invokeWithArguments(cat(mh,a));
         assertEquals(r0, r);
         mh = mh.asType(mh.type().changeParameterType(0x10, Integer.class));
@@ -808,10 +928,16 @@
     a[0xE0], a[0xE1], a[0xE2], a[0xE3], a[0xE4], a[0xE5], a[0xE6], a[0xE7], a[0xE8], a[0xE9], a[0xEA], a[0xEB], a[0xEC], a[0xED], a[0xEE], a[0xEF],
     a[0xF0], a[0xF1], a[0xF2], a[0xF3], a[0xF4], a[0xF5], a[0xF6], a[0xF7],
     // </editor-fold>
-    a[0xF8], a[0xF9], a[0xFA], a[0xFB], a[0xFC], a[0xFD]);
+    a[0xF8], a[0xF9], a[0xFA], a[0xFB], a[0xFC], a[0xFD]); // hashArguments_254
         assertEquals(r0, r);
         MethodType mt = MethodType.genericMethodType(ARITY);
         MethodHandle mh = MethodHandles.lookup().findStatic(BigArityTest.class, "hashArguments_"+ARITY, mt);
+        test254(mh, a, r0);
+        MethodHandle mh_CA = MH_hashArguments_VA.asFixedArity().asCollector(Object[].class, ARITY);
+        test254(mh_CA, a, r0);
+    }
+    public void test254(MethodHandle mh, Object[] a, Object r0) throws Throwable {
+        Object r;
         r = mh.invokeExact(
     // <editor-fold defaultstate="collapsed" desc="a[0x00], a[0x01], a[0x02], a[0x03], a[0x04], ...">
     a[0x00], a[0x01], a[0x02], a[0x03], a[0x04], a[0x05], a[0x06], a[0x07], a[0x08], a[0x09], a[0x0A], a[0x0B], a[0x0C], a[0x0D], a[0x0E], a[0x0F],
@@ -833,7 +959,6 @@
     // </editor-fold>
     a[0xF8], a[0xF9], a[0xFA], a[0xFB], a[0xFC], a[0xFD]);
         assertEquals(r0, r);
-        // FIXME: This next one fails, because it uses an internal invoker of arity 255.
         r = mh.invokeWithArguments(a);
         assertEquals(r0, r);
         try {
@@ -998,7 +1123,7 @@
     a[0xE0], a[0xE1], a[0xE2], a[0xE3], a[0xE4], a[0xE5], a[0xE6], a[0xE7], a[0xE8], a[0xE9], a[0xEA], a[0xEB], a[0xEC], a[0xED], a[0xEE], a[0xEF],
     a[0xF0], a[0xF1], a[0xF2], a[0xF3], a[0xF4], a[0xF5], a[0xF6], a[0xF7],
     // </editor-fold>
-    a[0xF8], a[0xF9], a[0xFA], a[0xFB], a[0xFC], a[0xFD], a[0xFE]);
+    a[0xF8], a[0xF9], a[0xFA], a[0xFB], a[0xFC], a[0xFD], a[0xFE]); // hashArguments_255
         assertEquals(r0, r);
         MethodType mt = MethodType.genericMethodType(ARITY);
         MethodHandle mh;