8001108: an attempt to use "<init>" as a method name should elicit NoSuchMethodException

Add an explicit check for leading "<", upgrade the unit tests

Reviewed-by: twisti, darcy
This commit is contained in:
John R Rose 2013-10-05 05:30:39 -07:00
parent e4df9dc1ee
commit 154969a2bc
3 changed files with 265 additions and 33 deletions

View File

@ -289,6 +289,9 @@ public class MethodHandles {
* 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.
@ -305,6 +308,12 @@ public class MethodHandles {
* (which will necessarily be a superclass of the lookup class)
* to the lookup class itself.
* <p>
* The JVM represents constructors and static initializer blocks as internal methods
* with special names ({@code "<init>"} and {@code "<clinit>"}).
* The internal syntax of invocation instructions allows them to refer to such internal
* methods as if they were normal methods, but the JVM bytecode verifier rejects them.
* A lookup of such an internal method will produce a {@code NoSuchMethodException}.
* <p>
* 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.
@ -604,6 +613,15 @@ public class MethodHandles {
* 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.
* <b>Example:</b>
* <p><blockquote><pre>{@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());
* }</pre></blockquote>
* @param refc the class from which the method is accessed
* @param name the name of the method
* @param type the type of the method
@ -654,6 +672,34 @@ public class MethodHandles {
* {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker}
* with the same {@code type} argument.
*
* <b>Example:</b>
* <p><blockquote><pre>{@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, "<init>", MT_newString));
} catch (NoSuchMethodException ex) { } // OK
MethodHandle MH_newString = publicLookup()
.findConstructor(String.class, MT_newString);
assertEquals("", (String) MH_newString.invokeExact());
* }</pre></blockquote>
*
* @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
@ -696,12 +742,30 @@ public class MethodHandles {
* If the constructor's class has not yet been initialized, that is done
* immediately, before the method handle is returned.
* <p>
* Note: The requested type must have a return type of {@code void}.
* This is consistent with the JVM's treatment of constructor type descriptors.
* <em>(Note: The requested type must have a return type of {@code void}.
* This is consistent with the JVM's treatment of constructor type descriptors.)</em>
* <p>
* 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.
* <p><b>Example:</b>
* <p><blockquote><pre>{@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());
* }</pre></blockquote>
* @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
@ -741,6 +805,45 @@ public class MethodHandles {
* 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.
* <p>
* <em>(Note: JVM internal methods named {@code "<init>"} are 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.)</em>
* <p><b>Example:</b>
* <p><blockquote><pre>{@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, "<init>", 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
* }</pre></blockquote>
*
* @param refc the class or interface from which the method is accessed
* @param name the name of the method (which must not be "&lt;init&gt;")
* @param type the type of the method, with the receiver argument omitted
@ -1105,6 +1208,7 @@ return mh1;
checkSymbolicClass(refc); // do this before attempting to resolve
name.getClass(); // NPE
type.getClass(); // NPE
checkMethodName(refKind, name); // NPE check on name
return IMPL_NAMES.resolveOrFail(refKind, new MemberName(refc, name, type, refKind), lookupClassOrNull(),
NoSuchMethodException.class);
}
@ -1124,6 +1228,13 @@ return mh1;
throw new MemberName(refc).makeAccessException("symbolic reference class is not public", this);
}
/** Check name for an illegal leading "<" character. */
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.

View File

@ -53,7 +53,11 @@ public class JavaDocExamplesTest {
}
public void run() throws Throwable {
testMisc();
testFindStatic();
testFindConstructor();
testFindVirtual();
testFindSpecial();
testPermuteArguments();
testDropArguments();
testFilterArguments();
@ -99,7 +103,8 @@ static {
{}
@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));
@ -114,6 +119,92 @@ assertEquals("xy".hashCode(), (int) HASHCODE_3.invokeExact((Object)"xy"));
{}
}
@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, "<init>", 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, "<init>", 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

View File

@ -387,6 +387,7 @@ public class MethodHandlesTest {
protected Example(String name) { this.name = name; }
@SuppressWarnings("LeakingThisInConstructor")
protected Example(int x) { this(); called("protected <init>", this, x); }
//Example(Void x) { does not exist; lookup elicts NoSuchMethodException }
@Override public String toString() { return name; }
public void v0() { called("v0", this); }
@ -487,6 +488,9 @@ public class MethodHandlesTest {
return lookup.in(defc);
}
/** Is findVirtual (etc.) of "<init>" supposed to elicit a NoSuchMethodException? */
final static boolean INIT_REF_CAUSES_NSME = true;
@Test
public void testFindStatic() throws Throwable {
if (CAN_SKIP_WORKING) return;
@ -507,6 +511,8 @@ public class MethodHandlesTest {
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, "<init>", int.class);
testFindStatic(false, PRIVATE, Example.class, void.class, "<init>", Void.class);
testFindStatic(false, PRIVATE, Example.class, void.class, "v0");
}
@ -529,11 +535,12 @@ public class MethodHandlesTest {
target = maybeMoveIn(lookup, defc).findStatic(defc, methodName, type);
} catch (ReflectiveOperationException ex) {
noAccess = ex;
assertExceptionClass(
(name.contains("bogus") || INIT_REF_CAUSES_NSME && name.contains("<init>"))
? 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
@ -551,6 +558,13 @@ public class MethodHandlesTest {
System.out.print(':');
}
static void assertExceptionClass(Class<? extends Throwable> 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
@ -580,6 +594,8 @@ public class MethodHandlesTest {
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, "<init>", int.class);
testFindVirtual(false, PRIVATE, Example.class, Example.class, void.class, "<init>", Void.class);
testFindVirtual(false, PRIVATE, Example.class, Example.class, void.class, "s0");
// test dispatch
@ -628,11 +644,12 @@ public class MethodHandlesTest {
target = maybeMoveIn(lookup, defc).findVirtual(defc, methodName, type);
} catch (ReflectiveOperationException ex) {
noAccess = ex;
assertExceptionClass(
(name.contains("bogus") || INIT_REF_CAUSES_NSME && name.contains("<init>"))
? 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
@ -682,11 +699,11 @@ public class MethodHandlesTest {
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, "<init>", int.class);
testFindSpecial(false, lookup, SubExample.class, Example.class, void.class, "<init>", Void.class);
testFindSpecial(false, lookup, SubExample.class, Example.class, void.class, "s0");
}
}
@ -712,19 +729,25 @@ public class MethodHandlesTest {
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("<init>"))
? 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
@ -769,7 +792,7 @@ public class MethodHandlesTest {
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()+".<init>/"+type+" => "+target
@ -800,6 +823,8 @@ public class MethodHandlesTest {
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, "<init>", int.class);
testBind(false, PRIVATE, Example.class, void.class, "<init>", 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");
@ -823,11 +848,12 @@ public class MethodHandlesTest {
target = maybeMoveIn(lookup, defc).bind(receiver, methodName, type);
} catch (ReflectiveOperationException ex) {
noAccess = ex;
assertExceptionClass(
(name.contains("bogus") || INIT_REF_CAUSES_NSME && name.contains("<init>"))
? 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
@ -890,6 +916,10 @@ public class MethodHandlesTest {
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;
@ -898,16 +928,15 @@ public class MethodHandlesTest {
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
@ -1141,11 +1170,12 @@ public class MethodHandlesTest {
} 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