changeset 450:2168949aaeb6

meth-arity-8019417.patch: first cut
author jrose
date Mon, 08 Jul 2013 17:57:33 -0700
parents f7504de08f94
children 1a78d97d1283
files meth-arity-8019417.patch series
diffstat 2 files changed, 234 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/meth-arity-8019417.patch	Mon Jul 08 17:57:33 2013 -0700
@@ -0,0 +1,233 @@
+8019417: JSR 292 javadoc should clarify method handle arity limits
+Summary: clarification of erroneous reading of spec. that led to 7194534
+Reviewed-by: ?
+
+The specification for MethodHandle guarantees that each method handle has a method called invokeExact (and also invoke):
+ * A method handle contains a pair of special invoker methods
+ * called {@link #invokeExact invokeExact} and {@link #invoke invoke}.
+
+Because of JVM limits on the arity of methods, any non-static method can take at most 254 arguments, for total argument count (including receiver) of 255. The limit is lower if there are doubles or longs.
+
+Therefore, a method handle's invokeExact method, and hence its type, cannot have an arity larger than 254.
+
+It is therefore impossible to:
+
+* lookup (reify as a method handle) a static method with arity 255
+* lookup (reify as a method handle) a non-static method or constructor with arity 254
+* create a dropArguments or permuteArguments transform which increases the resulting arity to 255 or more
+* create a collector transform which collects zero arguments and increases the resulting arity to 255 or more
+* request an asType or explicitCastArguments conversion of a method handle to a type of arity 255
+* create an exactInvoker or generic invoker for method handles with arity 254
+* create a spreadInvoker for method handles with arity 254 which spreads less than two arguments
+
+All of these attempts throw (or should throw) IllegalArgumentException.
+
+The mentioned limits decrease if longs or doubles are present, because longs or doubles count (relative to the JVM limit of 255) as two argument slots.
+
+For example, since MethodHandles.exactInvoker produces a method handle which invokes its first argument, it follows that the exact invoker's argument can have an arity of no more than 253. I.e., you cannot make an exactInvoker (or generic invoker) for a method handle type with a maximal arity of 254.
+
+It is theoretically possible to delay the throw by creating a "dummy" MH that refuses its inputs, but the 292 API generally has a fail-fast design in such corner cases. For example, an asType that requests an impossible conversion will throw WrongMethodTypeException, rather than deferring to a later invocation of the broken method handle.
+
+The preceding points are not changes to the original JSR 292 specification, but rather necessary implications of that specification, when combined with the basic limitation of JVM methods to receive at most 255 arguments.  Therefore, this bug calls for an editorial change to the javadoc, rather than a change to the specification.  It may suggest additional compliance tests, but does not require or allow behavior inconsistent with the original specification.
+
+diff --git a/src/share/classes/java/lang/invoke/MethodHandle.java b/src/share/classes/java/lang/invoke/MethodHandle.java
+--- a/src/share/classes/java/lang/invoke/MethodHandle.java
++++ b/src/share/classes/java/lang/invoke/MethodHandle.java
+@@ -392,16 +392,32 @@
+  * 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>
+  * <li>Higher order functions over method handles (combinators) are
+- * often generic across a wide range of function types, including
++ * often generic across a wide range of function types, includingf
+  * those of multiple arities.  It is impossible to represent such
+  * 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 &ldquo;receiver&rldquo; 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 &ldquo;receiver&rldquo; 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
+diff --git a/src/share/classes/java/lang/invoke/MethodHandles.java b/src/share/classes/java/lang/invoke/MethodHandles.java
+--- a/src/share/classes/java/lang/invoke/MethodHandles.java
++++ b/src/share/classes/java/lang/invoke/MethodHandles.java
+@@ -242,6 +242,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>
+diff --git a/test/java/lang/invoke/BigArityTest.java b/test/java/lang/invoke/BigArityTest.java
+--- a/test/java/lang/invoke/BigArityTest.java
++++ b/test/java/lang/invoke/BigArityTest.java
+@@ -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);
+@@ -292,7 +382,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);
+@@ -648,7 +738,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 +763,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));
+@@ -833,7 +921,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 {
--- a/series	Wed Jul 03 12:53:13 2013 -0700
+++ b/series	Mon Jul 08 17:57:33 2013 -0700
@@ -6,6 +6,7 @@
 # review pending before push to hotspot-comp (or tl):
 meth-doc-8014634.patch          #-/meth #+53be90fb39d6
 meth-info-8008688.patch         #-/meth #+53be90fb39d6
+meth-arity-8019417.patch        #-/meth #+53be90fb39d6
 
 # non-pushed files are under review or development, or merely experimental:
 anno-stable-8001107.patch       #-/meth #+53be90fb39d6 #-testable