# HG changeset patch # User kizune # Date 1397034993 -14400 # Node ID e74bc78d7d503811efe35ac9f2935b044a3ad8d4 # Parent e4ad481e95031aabb4c789dda1e56d1e3d542131 8001108: an attempt to use "" as a method name should elicit NoSuchMethodException Reviewed-by: twisti diff -r e4ad481e9503 -r e74bc78d7d50 src/share/classes/java/lang/invoke/MethodHandles.java --- a/src/share/classes/java/lang/invoke/MethodHandles.java Fri Apr 25 13:33:04 2014 +0400 +++ b/src/share/classes/java/lang/invoke/MethodHandles.java Wed Apr 09 13:16:33 2014 +0400 @@ -248,6 +248,9 @@ * In general, the conditions under which a method handle may be * looked up for a method {@code M} are exactly equivalent to the conditions * under which the lookup class could have compiled and resolved a call to {@code M}. + * Where the JVM would raise exceptions like {@code NoSuchMethodError}, + * a method handle lookup will generally raise a corresponding + * checked exception, such as {@code NoSuchMethodException}. * And the effect of invoking the method handle resulting from the lookup * is exactly equivalent to executing the compiled and resolved call to {@code M}. * The same point is true of fields and constructors. @@ -264,6 +267,12 @@ * (which will necessarily be a superclass of the lookup class) * to the lookup class itself. *

+ * The JVM represents constructors and static initializer blocks as internal methods + * with special names ({@code ""} and {@code ""}). + * The internal syntax of invocation instructions allows them to refer to such internal + * methods as if they were normal methods, but the JVM verifier rejects them. + * A lookup of such an internal method will produce a {@code NoSuchMethodException}. + *

* In some cases, access between nested classes is obtained by the Java compiler by creating * an wrapper method to access a private method of another class * in the same top-level declaration. @@ -578,6 +587,15 @@ * The returned method handle will have * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if * the method's variable arity modifier bit ({@code 0x0080}) is set. + * Example: + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle MH_asList = publicLookup().findStatic(Arrays.class,
+  "asList", methodType(List.class, Object[].class));
+assertEquals("[x, y]", MH_asList.invoke("x", "y").toString());
+         * }
* @param refc the class from which the method is accessed * @param name the name of the method * @param type the type of the method @@ -628,6 +646,34 @@ * {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker} * with the same {@code type} argument. * + * Example: + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle MH_concat = publicLookup().findVirtual(String.class,
+  "concat", methodType(String.class, String.class));
+MethodHandle MH_hashCode = publicLookup().findVirtual(Object.class,
+  "hashCode", methodType(int.class));
+MethodHandle MH_hashCode_String = publicLookup().findVirtual(String.class,
+  "hashCode", methodType(int.class));
+assertEquals("xy", (String) MH_concat.invokeExact("x", "y"));
+assertEquals("xy".hashCode(), (int) MH_hashCode.invokeExact((Object)"xy"));
+assertEquals("xy".hashCode(), (int) MH_hashCode_String.invokeExact("xy"));
+// interface method:
+MethodHandle MH_subSequence = publicLookup().findVirtual(CharSequence.class,
+  "subSequence", methodType(CharSequence.class, int.class, int.class));
+assertEquals("def", MH_subSequence.invoke("abcdefghi", 3, 6).toString());
+// constructor "internal method" must be accessed differently:
+MethodType MT_newString = methodType(void.class); //()V for new String()
+try { assertEquals("impossible", lookup()
+        .findVirtual(String.class, "", MT_newString));
+ } catch (NoSuchMethodException ex) { } // OK
+MethodHandle MH_newString = publicLookup()
+  .findConstructor(String.class, MT_newString);
+assertEquals("", (String) MH_newString.invokeExact());
+         * }
+ * * @param refc the class or interface from which the method is accessed * @param name the name of the method * @param type the type of the method, with the receiver argument omitted @@ -669,12 +715,30 @@ * If the constructor's class has not yet been initialized, that is done * immediately, before the method handle is returned. *

- * Note: The requested type must have a return type of {@code void}. - * This is consistent with the JVM's treatment of constructor type descriptors. + * (Note: The requested type must have a return type of {@code void}. + * This is consistent with the JVM's treatment of constructor type descriptors.) *

* The returned method handle will have * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if * the constructor's variable arity modifier bit ({@code 0x0080}) is set. + * Example: + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle MH_newArrayList = publicLookup().findConstructor(
+  ArrayList.class, methodType(void.class, Collection.class));
+Collection orig = Arrays.asList("x", "y");
+Collection copy = (ArrayList) MH_newArrayList.invokeExact(orig);
+assert(orig != copy);
+assertEquals(orig, copy);
+// a variable-arity constructor:
+MethodHandle MH_newProcessBuilder = publicLookup().findConstructor(
+  ProcessBuilder.class, methodType(void.class, String[].class));
+ProcessBuilder pb = (ProcessBuilder)
+  MH_newProcessBuilder.invoke("x", "y", "z");
+assertEquals("[x, y, z]", pb.command().toString());
+         * }
* @param refc the class or interface from which the method is accessed * @param type the type of the method, with the receiver argument omitted, and a void return type * @return the desired method handle @@ -714,6 +778,45 @@ * The returned method handle will have * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if * the method's variable arity modifier bit ({@code 0x0080}) is set. + *

+ * (Note: JVM internal methods named {@code } not visible to this API, + * even though the {@code invokespecial} instruction can refer to them + * in special circumstances. Use {@link #findConstructor findConstructor} + * to access instance initialization methods in a safe manner.) + * Example: + *

{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+static class Listie extends ArrayList {
+  public String toString() { return "[wee Listie]"; }
+  static Lookup lookup() { return MethodHandles.lookup(); }
+}
+...
+// no access to constructor via invokeSpecial:
+MethodHandle MH_newListie = Listie.lookup()
+  .findConstructor(Listie.class, methodType(void.class));
+Listie l = (Listie) MH_newListie.invokeExact();
+try { assertEquals("impossible", Listie.lookup().findSpecial(
+        Listie.class, "", methodType(void.class), Listie.class));
+ } catch (NoSuchMethodException ex) { } // OK
+// access to super and self methods via invokeSpecial:
+MethodHandle MH_super = Listie.lookup().findSpecial(
+  ArrayList.class, "toString" , methodType(String.class), Listie.class);
+MethodHandle MH_this = Listie.lookup().findSpecial(
+  Listie.class, "toString" , methodType(String.class), Listie.class);
+MethodHandle MH_duper = Listie.lookup().findSpecial(
+  Object.class, "toString" , methodType(String.class), Listie.class);
+assertEquals("[]", (String) MH_super.invokeExact(l));
+assertEquals(""+l, (String) MH_this.invokeExact(l));
+assertEquals("[]", (String) MH_duper.invokeExact(l)); // ArrayList method
+try { assertEquals("inaccessible", Listie.lookup().findSpecial(
+        String.class, "toString", methodType(String.class), Listie.class));
+ } catch (IllegalAccessException ex) { } // OK
+Listie subl = new Listie() { public String toString() { return "[subclass]"; } };
+assertEquals(""+l, (String) MH_this.invokeExact(subl)); // Listie method
+         * }
+ * * @param refc the class or interface from which the method is accessed * @param name the name of the method (which must not be "<init>") * @param type the type of the method, with the receiver argument omitted @@ -1017,15 +1120,16 @@ /// Helper methods, all package-private. MemberName resolveOrFail(byte refKind, Class refc, String name, Class type) throws NoSuchFieldException, IllegalAccessException { + name.getClass(); type.getClass(); // NPE checkSymbolicClass(refc); // do this before attempting to resolve - name.getClass(); type.getClass(); // NPE return IMPL_NAMES.resolveOrFail(refKind, new MemberName(refc, name, type, refKind), lookupClassOrNull(), NoSuchFieldException.class); } MemberName resolveOrFail(byte refKind, Class refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { + type.getClass(); // NPE checkSymbolicClass(refc); // do this before attempting to resolve - name.getClass(); type.getClass(); // NPE + checkMethodName(refKind, name); return IMPL_NAMES.resolveOrFail(refKind, new MemberName(refc, name, type, refKind), lookupClassOrNull(), NoSuchMethodException.class); } @@ -1036,6 +1140,12 @@ throw new MemberName(refc).makeAccessException("symbolic reference class is not public", this); } + void checkMethodName(byte refKind, String name) throws NoSuchMethodException { + if (name.startsWith("<") && refKind != REF_newInvokeSpecial) + throw new NoSuchMethodException("illegal method name: "+name); + } + + /** * Find my trustable caller class if m is a caller sensitive method. * If this lookup object has private access, then the caller class is the lookupClass. diff -r e4ad481e9503 -r e74bc78d7d50 test/java/lang/invoke/JavaDocExamplesTest.java --- a/test/java/lang/invoke/JavaDocExamplesTest.java Fri Apr 25 13:33:04 2014 +0400 +++ b/test/java/lang/invoke/JavaDocExamplesTest.java Wed Apr 09 13:16:33 2014 +0400 @@ -64,7 +64,11 @@ new JavaDocExamplesTest().run(); } public void run() throws Throwable { + testMisc(); + testFindStatic(); + testFindConstructor(); testFindVirtual(); + testFindSpecial(); testPermuteArguments(); testDropArguments(); testFilterArguments(); @@ -110,7 +114,8 @@ {} - @Test public void testFindVirtual() throws Throwable { + @Test public void testMisc() throws Throwable { +// Extra tests, not from javadoc: {} MethodHandle CONCAT_3 = LOOKUP.findVirtual(String.class, "concat", methodType(String.class, String.class)); @@ -125,6 +130,92 @@ {} } + @Test public void testFindStatic() throws Throwable { +{} +MethodHandle MH_asList = publicLookup().findStatic(Arrays.class, + "asList", methodType(List.class, Object[].class)); +assertEquals("[x, y]", MH_asList.invoke("x", "y").toString()); +{} + } + + @Test public void testFindVirtual() throws Throwable { +{} +MethodHandle MH_concat = publicLookup().findVirtual(String.class, + "concat", methodType(String.class, String.class)); +MethodHandle MH_hashCode = publicLookup().findVirtual(Object.class, + "hashCode", methodType(int.class)); +MethodHandle MH_hashCode_String = publicLookup().findVirtual(String.class, + "hashCode", methodType(int.class)); +assertEquals("xy", (String) MH_concat.invokeExact("x", "y")); +assertEquals("xy".hashCode(), (int) MH_hashCode.invokeExact((Object)"xy")); +assertEquals("xy".hashCode(), (int) MH_hashCode_String.invokeExact("xy")); +// interface method: +MethodHandle MH_subSequence = publicLookup().findVirtual(CharSequence.class, + "subSequence", methodType(CharSequence.class, int.class, int.class)); +assertEquals("def", MH_subSequence.invoke("abcdefghi", 3, 6).toString()); +// constructor "internal method" must be accessed differently: +MethodType MT_newString = methodType(void.class); //()V for new String() +try { assertEquals("impossible", lookup() + .findVirtual(String.class, "", MT_newString)); + } catch (NoSuchMethodException ex) { } // OK +MethodHandle MH_newString = publicLookup() + .findConstructor(String.class, MT_newString); +assertEquals("", (String) MH_newString.invokeExact()); +{} + } + + @Test public void testFindConstructor() throws Throwable { +{} +MethodHandle MH_newArrayList = publicLookup().findConstructor( + ArrayList.class, methodType(void.class, Collection.class)); +Collection orig = Arrays.asList("x", "y"); +Collection copy = (ArrayList) MH_newArrayList.invokeExact(orig); +assert(orig != copy); +assertEquals(orig, copy); +// a variable-arity constructor: +MethodHandle MH_newProcessBuilder = publicLookup().findConstructor( + ProcessBuilder.class, methodType(void.class, String[].class)); +ProcessBuilder pb = (ProcessBuilder) + MH_newProcessBuilder.invoke("x", "y", "z"); +assertEquals("[x, y, z]", pb.command().toString()); +{} + } + +// for testFindSpecial +{} +static class Listie extends ArrayList { + public String toString() { return "[wee Listie]"; } + static Lookup lookup() { return MethodHandles.lookup(); } +} +{} + + @Test public void testFindSpecial() throws Throwable { +{} +// no access to constructor via invokeSpecial: +MethodHandle MH_newListie = Listie.lookup() + .findConstructor(Listie.class, methodType(void.class)); +Listie l = (Listie) MH_newListie.invokeExact(); +try { assertEquals("impossible", Listie.lookup().findSpecial( + Listie.class, "", methodType(void.class), Listie.class)); + } catch (NoSuchMethodException ex) { } // OK +// access to super and self methods via invokeSpecial: +MethodHandle MH_super = Listie.lookup().findSpecial( + ArrayList.class, "toString" , methodType(String.class), Listie.class); +MethodHandle MH_this = Listie.lookup().findSpecial( + Listie.class, "toString" , methodType(String.class), Listie.class); +MethodHandle MH_duper = Listie.lookup().findSpecial( + Object.class, "toString" , methodType(String.class), Listie.class); +assertEquals("[]", (String) MH_super.invokeExact(l)); +assertEquals(""+l, (String) MH_this.invokeExact(l)); +assertEquals("[]", (String) MH_duper.invokeExact(l)); // ArrayList method +try { assertEquals("inaccessible", Listie.lookup().findSpecial( + String.class, "toString", methodType(String.class), Listie.class)); + } catch (IllegalAccessException ex) { } // OK +Listie subl = new Listie() { public String toString() { return "[subclass]"; } }; +assertEquals(""+l, (String) MH_this.invokeExact(subl)); // Listie method +{} + } + @Test public void testPermuteArguments() throws Throwable { {{ {} /// JAVADOC diff -r e4ad481e9503 -r e74bc78d7d50 test/java/lang/invoke/MethodHandlesTest.java --- a/test/java/lang/invoke/MethodHandlesTest.java Fri Apr 25 13:33:04 2014 +0400 +++ b/test/java/lang/invoke/MethodHandlesTest.java Wed Apr 09 13:16:33 2014 +0400 @@ -363,6 +363,7 @@ protected Example(String name) { this.name = name; } @SuppressWarnings("LeakingThisInConstructor") protected Example(int x) { this(); called("protected ", this, x); } + //Example(Void x) { does not exist; lookup elicts NoSuchMethodException } @Override public String toString() { return name; } public void v0() { called("v0", this); } @@ -463,6 +464,9 @@ return lookup.in(defc); } + /** Is findVirtual (etc.) of "" supposed to elicit a NoSuchMethodException? */ + final static boolean INIT_REF_CAUSES_NSME = true; + @Test public void testFindStatic() throws Throwable { if (CAN_SKIP_WORKING) return; @@ -483,6 +487,8 @@ testFindStatic(Example.class, Object.class, "s7", float.class, double.class); testFindStatic(false, PRIVATE, Example.class, void.class, "bogus"); + testFindStatic(false, PRIVATE, Example.class, void.class, "", int.class); + testFindStatic(false, PRIVATE, Example.class, void.class, "", Void.class); testFindStatic(false, PRIVATE, Example.class, void.class, "v0"); } @@ -505,11 +511,12 @@ target = maybeMoveIn(lookup, defc).findStatic(defc, methodName, type); } catch (ReflectiveOperationException ex) { noAccess = ex; + assertExceptionClass( + (name.contains("bogus") || INIT_REF_CAUSES_NSME && name.contains("")) + ? NoSuchMethodException.class + : IllegalAccessException.class, + noAccess); if (verbosity >= 5) ex.printStackTrace(System.out); - if (name.contains("bogus")) - assertTrue(noAccess instanceof NoSuchMethodException); - else - assertTrue(noAccess instanceof IllegalAccessException); } if (verbosity >= 3) System.out.println("findStatic "+lookup+": "+defc.getName()+"."+name+"/"+type+" => "+target @@ -527,6 +534,13 @@ System.out.print(':'); } + static void assertExceptionClass(Class expected, + Throwable actual) { + if (expected.isInstance(actual)) return; + actual.printStackTrace(); + assertEquals(expected, actual.getClass()); + } + static final boolean DEBUG_METHOD_HANDLE_NAMES = Boolean.getBoolean("java.lang.invoke.MethodHandle.DEBUG_NAMES"); // rough check of name string @@ -556,6 +570,8 @@ testFindVirtual(PubExample.class, void.class, "Pub/pro_v0"); testFindVirtual(false, PRIVATE, Example.class, Example.class, void.class, "bogus"); + testFindVirtual(false, PRIVATE, Example.class, Example.class, void.class, "", int.class); + testFindVirtual(false, PRIVATE, Example.class, Example.class, void.class, "", Void.class); testFindVirtual(false, PRIVATE, Example.class, Example.class, void.class, "s0"); // test dispatch @@ -591,11 +607,12 @@ target = maybeMoveIn(lookup, defc).findVirtual(defc, methodName, type); } catch (ReflectiveOperationException ex) { noAccess = ex; + assertExceptionClass( + (name.contains("bogus") || INIT_REF_CAUSES_NSME && name.contains("")) + ? NoSuchMethodException.class + : IllegalAccessException.class, + noAccess); if (verbosity >= 5) ex.printStackTrace(System.out); - if (name.contains("bogus")) - assertTrue(noAccess instanceof NoSuchMethodException); - else - assertTrue(noAccess instanceof IllegalAccessException); } if (verbosity >= 3) System.out.println("findVirtual "+lookup+": "+defc.getName()+"."+name+"/"+type+" => "+target @@ -632,11 +649,11 @@ testFindSpecial(SubExample.class, Example.class, void.class, "pkg_v0"); testFindSpecial(RemoteExample.class, PubExample.class, void.class, "Pub/pro_v0"); // Do some negative testing: - testFindSpecial(false, EXAMPLE, SubExample.class, Example.class, void.class, "bogus"); - testFindSpecial(false, PRIVATE, SubExample.class, Example.class, void.class, "bogus"); for (Lookup lookup : new Lookup[]{ PRIVATE, EXAMPLE, PACKAGE, PUBLIC }) { testFindSpecial(false, lookup, Object.class, Example.class, void.class, "v0"); + testFindSpecial(false, lookup, SubExample.class, Example.class, void.class, "bogus"); testFindSpecial(false, lookup, SubExample.class, Example.class, void.class, "", int.class); + testFindSpecial(false, lookup, SubExample.class, Example.class, void.class, "", Void.class); testFindSpecial(false, lookup, SubExample.class, Example.class, void.class, "s0"); } } @@ -662,19 +679,25 @@ countTest(positive); String methodName = name.substring(1 + name.indexOf('/')); // foo/bar => foo MethodType type = MethodType.methodType(ret, params); + Lookup specialLookup = maybeMoveIn(lookup, specialCaller); + boolean specialAccessOK = (specialLookup.lookupClass() == specialCaller && + (specialLookup.lookupModes() & Lookup.PRIVATE) != 0); MethodHandle target = null; Exception noAccess = null; try { if (verbosity >= 4) System.out.println("lookup via "+lookup+" of "+defc+" "+name+type); - if (verbosity >= 5) System.out.println(" lookup => "+maybeMoveIn(lookup, specialCaller)); - target = maybeMoveIn(lookup, specialCaller).findSpecial(defc, methodName, type, specialCaller); + if (verbosity >= 5) System.out.println(" lookup => "+specialLookup); + target = specialLookup.findSpecial(defc, methodName, type, specialCaller); } catch (ReflectiveOperationException ex) { noAccess = ex; + assertExceptionClass( + (!specialAccessOK) // this check should happen first + ? IllegalAccessException.class + : (name.contains("bogus") || INIT_REF_CAUSES_NSME && name.contains("")) + ? NoSuchMethodException.class + : IllegalAccessException.class, + noAccess); if (verbosity >= 5) ex.printStackTrace(System.out); - if (name.contains("bogus")) - assertTrue(noAccess instanceof NoSuchMethodException); - else - assertTrue(noAccess instanceof IllegalAccessException); } if (verbosity >= 3) System.out.println("findSpecial from "+specialCaller.getName()+" to "+defc.getName()+"."+name+"/"+type+" => "+target @@ -719,7 +742,7 @@ target = lookup.findConstructor(defc, type); } catch (ReflectiveOperationException ex) { noAccess = ex; - assertTrue(noAccess instanceof IllegalAccessException); + assertTrue(noAccess.getClass().getName(), noAccess instanceof IllegalAccessException); } if (verbosity >= 3) System.out.println("findConstructor "+defc.getName()+"./"+type+" => "+target @@ -750,6 +773,8 @@ testBind(Example.class, Object.class, "v2", int.class, Object.class); testBind(Example.class, Object.class, "v2", int.class, int.class); testBind(false, PRIVATE, Example.class, void.class, "bogus"); + testBind(false, PRIVATE, Example.class, void.class, "", int.class); + testBind(false, PRIVATE, Example.class, void.class, "", Void.class); testBind(SubExample.class, void.class, "Sub/v0"); testBind(SubExample.class, void.class, "Sub/pkg_v0"); testBind(IntExample.Impl.class, void.class, "Int/v0"); @@ -773,11 +798,12 @@ target = maybeMoveIn(lookup, defc).bind(receiver, methodName, type); } catch (ReflectiveOperationException ex) { noAccess = ex; + assertExceptionClass( + (name.contains("bogus") || INIT_REF_CAUSES_NSME && name.contains("")) + ? NoSuchMethodException.class + : IllegalAccessException.class, + noAccess); if (verbosity >= 5) ex.printStackTrace(System.out); - if (name.contains("bogus")) - assertTrue(noAccess instanceof NoSuchMethodException); - else - assertTrue(noAccess instanceof IllegalAccessException); } if (verbosity >= 3) System.out.println("bind "+receiver+"."+name+"/"+type+" => "+target @@ -840,6 +866,10 @@ countTest(positive); String methodName = name.substring(1 + name.indexOf('/')); // foo/bar => foo MethodType type = MethodType.methodType(ret, params); + Lookup specialLookup = (specialCaller != null ? maybeMoveIn(lookup, specialCaller) : null); + boolean specialAccessOK = (specialCaller != null && + specialLookup.lookupClass() == specialCaller && + (specialLookup.lookupModes() & Lookup.PRIVATE) != 0); Method rmethod = defc.getDeclaredMethod(methodName, params); MethodHandle target = null; Exception noAccess = null; @@ -848,16 +878,15 @@ try { if (verbosity >= 4) System.out.println("lookup via "+lookup+" of "+defc+" "+name+type); if (isSpecial) - target = maybeMoveIn(lookup, specialCaller).unreflectSpecial(rmethod, specialCaller); + target = specialLookup.unreflectSpecial(rmethod, specialCaller); else target = maybeMoveIn(lookup, defc).unreflect(rmethod); } catch (ReflectiveOperationException ex) { noAccess = ex; + assertExceptionClass( + IllegalAccessException.class, // NSME is impossible, since it was already reflected + noAccess); if (verbosity >= 5) ex.printStackTrace(System.out); - if (name.contains("bogus")) - assertTrue(noAccess instanceof NoSuchMethodException); - else - assertTrue(noAccess instanceof IllegalAccessException); } if (verbosity >= 3) System.out.println("unreflect"+(isSpecial?"Special":"")+" "+defc.getName()+"."+name+"/"+type @@ -1091,11 +1120,12 @@ } catch (ReflectiveOperationException ex) { mh = null; noAccess = ex; + assertExceptionClass( + (fname.contains("bogus")) + ? NoSuchFieldException.class + : IllegalAccessException.class, + noAccess); if (verbosity >= 5) ex.printStackTrace(System.out); - if (fname.contains("bogus")) - assertTrue(noAccess instanceof NoSuchFieldException); - else - assertTrue(noAccess instanceof IllegalAccessException); } if (verbosity >= 3) System.out.println("find"+(isStatic?"Static":"")+(isGetter?"Getter":"Setter")+" "+fclass.getName()+"."+fname+"/"+ftype