7087570: java.lang.invoke.MemberName information wrong for method handles created with findConstructor
Summary: REF_invokeSpecial DMHs (which are unusual) get marked explicitly; tweak the MHI to use this bit
Reviewed-by: jrose, twisti
--- a/jdk/src/share/classes/java/lang/invoke/DirectMethodHandle.java Thu Feb 21 20:01:22 2013 +0000
+++ b/jdk/src/share/classes/java/lang/invoke/DirectMethodHandle.java Tue Feb 26 11:05:26 2013 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2013, 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
@@ -55,8 +55,7 @@
}
// Factory methods:
-
- static DirectMethodHandle make(Class<?> receiver, MemberName member) {
+ static DirectMethodHandle make(byte refKind, Class<?> receiver, MemberName member) {
MethodType mtype = member.getMethodOrFieldType();
if (!member.isStatic()) {
if (!member.getDeclaringClass().isAssignableFrom(receiver) || member.isConstructor())
@@ -64,8 +63,14 @@
mtype = mtype.insertParameterTypes(0, receiver);
}
if (!member.isField()) {
- LambdaForm lform = preparedLambdaForm(member);
- return new DirectMethodHandle(mtype, lform, member);
+ if (refKind == REF_invokeSpecial) {
+ member = member.asSpecial();
+ LambdaForm lform = preparedLambdaForm(member);
+ return new Special(mtype, lform, member);
+ } else {
+ LambdaForm lform = preparedLambdaForm(member);
+ return new DirectMethodHandle(mtype, lform, member);
+ }
} else {
LambdaForm lform = preparedFieldLambdaForm(member);
if (member.isStatic()) {
@@ -79,6 +84,12 @@
}
}
}
+ static DirectMethodHandle make(Class<?> receiver, MemberName member) {
+ byte refKind = member.getReferenceKind();
+ if (refKind == REF_invokeSpecial)
+ refKind = REF_invokeVirtual;
+ return make(refKind, receiver, member);
+ }
static DirectMethodHandle make(MemberName member) {
if (member.isConstructor())
return makeAllocator(member);
@@ -114,6 +125,10 @@
//// Implementation methods.
@Override
+ MethodHandle viewAsType(MethodType newType) {
+ return new DirectMethodHandle(newType, form, member);
+ }
+ @Override
@ForceInline
MemberName internalMemberName() {
return member;
@@ -357,6 +372,21 @@
((DirectMethodHandle)mh).ensureInitialized();
}
+ /** This subclass represents invokespecial instructions. */
+ static class Special extends DirectMethodHandle {
+ private Special(MethodType mtype, LambdaForm form, MemberName member) {
+ super(mtype, form, member);
+ }
+ @Override
+ boolean isInvokeSpecial() {
+ return true;
+ }
+ @Override
+ MethodHandle viewAsType(MethodType newType) {
+ return new Special(newType, form, member);
+ }
+ }
+
/** This subclass handles constructor references. */
static class Constructor extends DirectMethodHandle {
final MemberName initMethod;
@@ -369,6 +399,10 @@
this.instanceClass = instanceClass;
assert(initMethod.isResolved());
}
+ @Override
+ MethodHandle viewAsType(MethodType newType) {
+ return new Constructor(newType, form, member, initMethod, instanceClass);
+ }
}
/*non-public*/ static Object constructorMethod(Object mh) {
@@ -395,6 +429,10 @@
@Override Object checkCast(Object obj) {
return fieldType.cast(obj);
}
+ @Override
+ MethodHandle viewAsType(MethodType newType) {
+ return new Accessor(newType, form, member, fieldOffset);
+ }
}
@ForceInline
@@ -434,6 +472,10 @@
@Override Object checkCast(Object obj) {
return fieldType.cast(obj);
}
+ @Override
+ MethodHandle viewAsType(MethodType newType) {
+ return new StaticAccessor(newType, form, member, staticBase, staticOffset);
+ }
}
@ForceInline
--- a/jdk/src/share/classes/java/lang/invoke/MethodHandle.java Thu Feb 21 20:01:22 2013 +0000
+++ b/jdk/src/share/classes/java/lang/invoke/MethodHandle.java Tue Feb 26 11:05:26 2013 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2013, 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
@@ -1250,8 +1250,6 @@
/*non-public*/
MethodHandle viewAsType(MethodType newType) {
// No actual conversions, just a new view of the same method.
- if (!type.isViewableAs(newType))
- throw new InternalError();
return MethodHandleImpl.makePairwiseConvert(this, newType, 0);
}
@@ -1268,6 +1266,11 @@
}
/*non-public*/
+ boolean isInvokeSpecial() {
+ return false; // DMH.Special returns true
+ }
+
+ /*non-public*/
Object internalValues() {
return null;
}
--- a/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java Thu Feb 21 20:01:22 2013 +0000
+++ b/jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java Tue Feb 26 11:05:26 2013 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2013, 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
@@ -367,11 +367,11 @@
@Override
MethodHandle viewAsType(MethodType newType) {
- MethodHandle mh = super.viewAsType(newType);
+ if (newType.lastParameterType() != type().lastParameterType())
+ throw new InternalError();
+ MethodHandle newTarget = asFixedArity().viewAsType(newType);
// put back the varargs bit:
- MethodType type = mh.type();
- int arity = type.parameterCount();
- return mh.asVarargsCollector(type.parameterType(arity-1));
+ return new AsVarargsCollector(newTarget, newType, arrayType);
}
@Override
@@ -379,6 +379,12 @@
return asFixedArity().internalMemberName();
}
+ /*non-public*/
+ @Override
+ boolean isInvokeSpecial() {
+ return asFixedArity().isInvokeSpecial();
+ }
+
@Override
MethodHandle bindArgument(int pos, char basicType, Object value) {
--- a/jdk/src/share/classes/java/lang/invoke/MethodHandleInfo.java Thu Feb 21 20:01:22 2013 +0000
+++ b/jdk/src/share/classes/java/lang/invoke/MethodHandleInfo.java Tue Feb 26 11:05:26 2013 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2013, 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
@@ -32,7 +32,6 @@
*/
final class MethodHandleInfo {
public static final int
- REF_NONE = Constants.REF_NONE,
REF_getField = Constants.REF_getField,
REF_getStatic = Constants.REF_getStatic,
REF_putField = Constants.REF_putField,
@@ -48,12 +47,17 @@
private final MethodType methodType;
private final int referenceKind;
- public MethodHandleInfo(MethodHandle mh) throws ReflectiveOperationException {
+ public MethodHandleInfo(MethodHandle mh) {
MemberName mn = mh.internalMemberName();
+ if (mn == null) throw new IllegalArgumentException("not a direct method handle");
this.declaringClass = mn.getDeclaringClass();
this.name = mn.getName();
- this.methodType = mn.getMethodType();
- this.referenceKind = mn.getReferenceKind();
+ this.methodType = mn.getMethodOrFieldType();
+ byte refKind = mn.getReferenceKind();
+ if (refKind == REF_invokeSpecial && !mh.isInvokeSpecial())
+ // Devirtualized method invocation is usually formally virtual.
+ refKind = REF_invokeVirtual;
+ this.referenceKind = refKind;
}
public Class<?> getDeclaringClass() {
@@ -78,7 +82,6 @@
static String getReferenceKindString(int referenceKind) {
switch (referenceKind) {
- case REF_NONE: return "REF_NONE";
case REF_getField: return "getfield";
case REF_getStatic: return "getstatic";
case REF_putField: return "putfield";
--- a/jdk/src/share/classes/java/lang/invoke/MethodHandles.java Thu Feb 21 20:01:22 2013 +0000
+++ b/jdk/src/share/classes/java/lang/invoke/MethodHandles.java Tue Feb 26 11:05:26 2013 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2013, 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
@@ -1209,7 +1209,7 @@
checkMethod(refKind, refc, method);
if (method.isMethodHandleInvoke())
return fakeMethodHandleInvoke(method);
- MethodHandle mh = DirectMethodHandle.make(refc, method);
+ MethodHandle mh = DirectMethodHandle.make(refKind, refc, method);
mh = maybeBindCaller(method, mh);
mh = mh.setVarargs(method);
if (doRestrict)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/7087570/Test7087570.java Tue Feb 26 11:05:26 2013 +0000
@@ -0,0 +1,326 @@
+/*
+ * Copyright (c) 2013, 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 7087570
+ * @summary REF_invokeSpecial DMHs (which are unusual) get marked explicitly; tweak the MHI to use this bit
+ *
+ * @run main Test7087570
+ */
+
+import java.lang.invoke.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+
+public class Test7087570 {
+ // XXX may remove the following constant declarations when MethodHandleInfo is made public
+ private static final int
+ REF_getField = 1,
+ REF_getStatic = 2,
+ REF_putField = 3,
+ REF_putStatic = 4,
+ REF_invokeVirtual = 5,
+ REF_invokeStatic = 6,
+ REF_invokeSpecial = 7,
+ REF_newInvokeSpecial = 8,
+ REF_invokeInterface = 9,
+ REF_LIMIT = 10;
+
+ private static final TestMethodData[] TESTS = new TestMethodData[] {
+ // field accessors
+ data(DummyFieldHolder.class, "instanceField", getterMethodType(String.class), DummyFieldHolder.class, REF_getField),
+ data(DummyFieldHolder.class, "instanceField", setterMethodType(String.class), DummyFieldHolder.class, REF_putField),
+ data(DummyFieldHolder.class, "staticField", getterMethodType(Integer.class), DummyFieldHolder.class, REF_getStatic),
+ data(DummyFieldHolder.class, "staticField", setterMethodType(Integer.class), DummyFieldHolder.class, REF_putStatic),
+ data(DummyFieldHolder.class, "instanceByteField", getterMethodType(byte.class), DummyFieldHolder.class, REF_getField),
+ data(DummyFieldHolder.class, "instanceByteField", setterMethodType(byte.class), DummyFieldHolder.class, REF_putField),
+
+ // REF_invokeVirtual
+ data(Object.class, "hashCode", methodType(int.class), Object.class, REF_invokeVirtual),
+
+ // REF_invokeVirtual strength-reduced to REF_invokeSpecial,
+ // test if it normalizes back to REF_invokeVirtual in MethodHandleInfo as expected
+ data(String.class, "hashCode", methodType(int.class), String.class, REF_invokeVirtual),
+
+ // REF_invokeStatic
+ data(Collections.class, "sort", methodType(void.class, List.class), Collections.class, REF_invokeStatic),
+ data(Arrays.class, "asList", methodType(List.class, Object[].class), Arrays.class, REF_invokeStatic), // varargs case
+
+ // REF_invokeSpecial
+ data(Object.class, "hashCode", methodType(int.class), Object.class, REF_invokeSpecial),
+
+ // REF_newInvokeSpecial
+ data(String.class, "<init>", methodType(void.class, char[].class), String.class, REF_newInvokeSpecial),
+ data(DummyFieldHolder.class, "<init>", methodType(void.class, byte.class, Long[].class), DummyFieldHolder.class, REF_newInvokeSpecial), // varargs case
+
+ // REF_invokeInterface
+ data(List.class, "size", methodType(int.class), List.class, REF_invokeInterface)
+ };
+
+ public static void main(String... args) throws Throwable {
+ testWithLookup();
+ testWithUnreflect();
+ }
+
+ private static void doTest(MethodHandle mh, TestMethodData testMethod) {
+ Object mhi = newMethodHandleInfo(mh);
+
+ System.out.printf("%s.%s: %s, nominal refKind: %s, actual refKind: %s\n",
+ testMethod.clazz.getName(), testMethod.name, testMethod.methodType,
+ REF_KIND_NAMES[testMethod.referenceKind],
+ REF_KIND_NAMES[getReferenceKind(mhi)]);
+ assertEquals(testMethod.name, getName(mhi));
+ assertEquals(testMethod.methodType, getMethodType(mhi));
+ assertEquals(testMethod.declaringClass, getDeclaringClass(mhi));
+ assertEquals(testMethod.referenceKind == REF_invokeSpecial, isInvokeSpecial(mh));
+ assertRefKindEquals(testMethod.referenceKind, getReferenceKind(mhi));
+ }
+
+ private static void testWithLookup() throws Throwable {
+ for (TestMethodData testMethod : TESTS) {
+ MethodHandle mh = lookupFrom(testMethod);
+ doTest(mh, testMethod);
+ }
+ }
+
+ private static void testWithUnreflect() throws Throwable {
+ for (TestMethodData testMethod : TESTS) {
+ MethodHandle mh = unreflectFrom(testMethod);
+ doTest(mh, testMethod);
+ }
+ }
+
+ private static MethodType getterMethodType(Class<?> clazz) {
+ return methodType(clazz);
+ }
+
+ private static MethodType setterMethodType(Class<?> clazz) {
+ return methodType(void.class, clazz);
+ }
+
+ private static final String[] REF_KIND_NAMES = {
+ "MH::invokeBasic",
+ "REF_getField", "REF_getStatic", "REF_putField", "REF_putStatic",
+ "REF_invokeVirtual", "REF_invokeStatic", "REF_invokeSpecial",
+ "REF_newInvokeSpecial", "REF_invokeInterface"
+ };
+
+ private static final Lookup LOOKUP = lookup();
+
+ // XXX may remove the following reflective logic when MethodHandleInfo is made public
+ private static final MethodHandle MH_IS_INVOKESPECIAL;
+ private static final MethodHandle MHI_CONSTRUCTOR;
+ private static final MethodHandle MHI_GET_NAME;
+ private static final MethodHandle MHI_GET_METHOD_TYPE;
+ private static final MethodHandle MHI_GET_DECLARING_CLASS;
+ private static final MethodHandle MHI_GET_REFERENCE_KIND;
+
+ static {
+ try {
+ // This is white box testing. Use reflection to grab private implementation bits.
+ String magicName = "IMPL_LOOKUP";
+ Field magicLookup = MethodHandles.Lookup.class.getDeclaredField(magicName);
+ // This unit test will fail if a security manager is installed.
+ magicLookup.setAccessible(true);
+ // Forbidden fruit...
+ Lookup directInvokeLookup = (Lookup) magicLookup.get(null);
+ Class<?> mhiClass = Class.forName("java.lang.invoke.MethodHandleInfo", false, MethodHandle.class.getClassLoader());
+ MH_IS_INVOKESPECIAL = directInvokeLookup
+ .findVirtual(MethodHandle.class, "isInvokeSpecial", methodType(boolean.class));
+ MHI_CONSTRUCTOR = directInvokeLookup
+ .findConstructor(mhiClass, methodType(void.class, MethodHandle.class));
+ MHI_GET_NAME = directInvokeLookup
+ .findVirtual(mhiClass, "getName", methodType(String.class));
+ MHI_GET_METHOD_TYPE = directInvokeLookup
+ .findVirtual(mhiClass, "getMethodType", methodType(MethodType.class));
+ MHI_GET_DECLARING_CLASS = directInvokeLookup
+ .findVirtual(mhiClass, "getDeclaringClass", methodType(Class.class));
+ MHI_GET_REFERENCE_KIND = directInvokeLookup
+ .findVirtual(mhiClass, "getReferenceKind", methodType(int.class));
+ } catch (ReflectiveOperationException ex) {
+ throw new Error(ex);
+ }
+ }
+
+ private static class TestMethodData {
+ final Class<?> clazz;
+ final String name;
+ final MethodType methodType;
+ final Class<?> declaringClass;
+ final int referenceKind; // the nominal refKind
+
+ public TestMethodData(Class<?> clazz, String name,
+ MethodType methodType, Class<?> declaringClass,
+ int referenceKind) {
+ this.clazz = clazz;
+ this.name = name;
+ this.methodType = methodType;
+ this.declaringClass = declaringClass;
+ this.referenceKind = referenceKind;
+ }
+ }
+
+ private static TestMethodData data(Class<?> clazz, String name,
+ MethodType methodType, Class<?> declaringClass,
+ int referenceKind) {
+ return new TestMethodData(clazz, name, methodType, declaringClass, referenceKind);
+ }
+
+ private static MethodHandle lookupFrom(TestMethodData testMethod)
+ throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException {
+ switch (testMethod.referenceKind) {
+ case REF_getField:
+ return LOOKUP.findGetter(testMethod.clazz, testMethod.name, testMethod.methodType.returnType());
+ case REF_putField:
+ return LOOKUP.findSetter(testMethod.clazz, testMethod.name, testMethod.methodType.parameterType(0));
+ case REF_getStatic:
+ return LOOKUP.findStaticGetter(testMethod.clazz, testMethod.name, testMethod.methodType.returnType());
+ case REF_putStatic:
+ return LOOKUP.findStaticSetter(testMethod.clazz, testMethod.name, testMethod.methodType.parameterType(0));
+ case REF_invokeVirtual:
+ case REF_invokeInterface:
+ return LOOKUP.findVirtual(testMethod.clazz, testMethod.name, testMethod.methodType);
+ case REF_invokeStatic:
+ return LOOKUP.findStatic(testMethod.clazz, testMethod.name, testMethod.methodType);
+ case REF_invokeSpecial:
+ Class<?> thisClass = LOOKUP.lookupClass();
+ return LOOKUP.findSpecial(testMethod.clazz, testMethod.name, testMethod.methodType, thisClass);
+ case REF_newInvokeSpecial:
+ return LOOKUP.findConstructor(testMethod.clazz, testMethod.methodType);
+ default:
+ throw new Error("ERROR: unexpected referenceKind in test data");
+ }
+ }
+
+ private static MethodHandle unreflectFrom(TestMethodData testMethod)
+ throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException {
+ switch (testMethod.referenceKind) {
+ case REF_getField:
+ case REF_getStatic: {
+ Field f = testMethod.clazz.getDeclaredField(testMethod.name);
+ return LOOKUP.unreflectGetter(f);
+ }
+ case REF_putField:
+ case REF_putStatic: {
+ Field f = testMethod.clazz.getDeclaredField(testMethod.name);
+ return LOOKUP.unreflectSetter(f);
+ }
+ case REF_invokeVirtual:
+ case REF_invokeStatic:
+ case REF_invokeInterface: {
+ Method m = testMethod.clazz.getDeclaredMethod(testMethod.name, testMethod.methodType.parameterArray());
+ return LOOKUP.unreflect(m);
+ }
+ case REF_invokeSpecial: {
+ Method m = testMethod.clazz.getDeclaredMethod(testMethod.name, testMethod.methodType.parameterArray());
+ Class<?> thisClass = LOOKUP.lookupClass();
+ return LOOKUP.unreflectSpecial(m, thisClass);
+ }
+ case REF_newInvokeSpecial: {
+ Constructor c = testMethod.clazz.getDeclaredConstructor(testMethod.methodType.parameterArray());
+ return LOOKUP.unreflectConstructor(c);
+ }
+ default:
+ throw new Error("ERROR: unexpected referenceKind in test data");
+ }
+ }
+
+ private static Object newMethodHandleInfo(MethodHandle mh) {
+ try {
+ return MHI_CONSTRUCTOR.invoke(mh);
+ } catch (Throwable ex) {
+ throw new Error(ex);
+ }
+ }
+
+ private static boolean isInvokeSpecial(MethodHandle mh) {
+ try {
+ return (boolean) MH_IS_INVOKESPECIAL.invokeExact(mh);
+ } catch (Throwable ex) {
+ throw new Error(ex);
+ }
+ }
+
+ private static String getName(Object mhi) {
+ try {
+ return (String) MHI_GET_NAME.invoke(mhi);
+ } catch (Throwable ex) {
+ throw new Error(ex);
+ }
+ }
+
+ private static MethodType getMethodType(Object mhi) {
+ try {
+ return (MethodType) MHI_GET_METHOD_TYPE.invoke(mhi);
+ } catch (Throwable ex) {
+ throw new Error(ex);
+ }
+ }
+
+ private static Class<?> getDeclaringClass(Object mhi) {
+ try {
+ return (Class<?>) MHI_GET_DECLARING_CLASS.invoke(mhi);
+ } catch (Throwable ex) {
+ throw new Error(ex);
+ }
+ }
+
+ private static int getReferenceKind(Object mhi) {
+ try {
+ return (int) MHI_GET_REFERENCE_KIND.invoke(mhi);
+ } catch (Throwable ex) {
+ throw new Error(ex);
+ }
+ }
+
+ private static void assertRefKindEquals(int expect, int observed) {
+ if (expect == observed) return;
+
+ String msg = "expected " + REF_KIND_NAMES[(int) expect] +
+ " but observed " + REF_KIND_NAMES[(int) observed];
+ System.out.println("FAILED: " + msg);
+ throw new AssertionError(msg);
+ }
+
+ private static void assertEquals(Object expect, Object observed) {
+ if (java.util.Objects.equals(expect, observed)) return;
+
+ String msg = "expected " + expect + " but observed " + observed;
+ System.out.println("FAILED: " + msg);
+ throw new AssertionError(msg);
+ }
+}
+
+class DummyFieldHolder {
+ public static Integer staticField;
+ public String instanceField;
+ public byte instanceByteField;
+
+ public DummyFieldHolder(byte unused1, Long... unused2) {
+ }
+}
+