# HG changeset patch # User kmo # Date 1361876726 0 # Node ID 265a0d86c9ffa47a61f79e3f47373599d68203a3 # Parent b57c48f161797a72a681e119021a40c1af5ee98e 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 diff -r b57c48f16179 -r 265a0d86c9ff jdk/src/share/classes/java/lang/invoke/DirectMethodHandle.java --- 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 diff -r b57c48f16179 -r 265a0d86c9ff jdk/src/share/classes/java/lang/invoke/MethodHandle.java --- 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; } diff -r b57c48f16179 -r 265a0d86c9ff jdk/src/share/classes/java/lang/invoke/MethodHandleImpl.java --- 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) { diff -r b57c48f16179 -r 265a0d86c9ff jdk/src/share/classes/java/lang/invoke/MethodHandleInfo.java --- 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"; diff -r b57c48f16179 -r 265a0d86c9ff jdk/src/share/classes/java/lang/invoke/MethodHandles.java --- 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) diff -r b57c48f16179 -r 265a0d86c9ff jdk/test/java/lang/invoke/7087570/Test7087570.java --- /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, "", methodType(void.class, char[].class), String.class, REF_newInvokeSpecial), + data(DummyFieldHolder.class, "", 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) { + } +} +