changeset 446:0591b53a4471

meth-info-8008688.patch: better tests, including SM
author jrose
date Wed, 26 Jun 2013 22:38:32 -0700
parents 08add1bcd8eb
children a49289c99587
files meth-info-8008688.patch
diffstat 1 files changed, 610 insertions(+), 107 deletions(-) [+]
line wrap: on
line diff
--- a/meth-info-8008688.patch	Mon Jun 17 18:59:01 2013 -0700
+++ b/meth-info-8008688.patch	Wed Jun 26 22:38:32 2013 -0700
@@ -24,7 +24,7 @@
 new file mode 100644
 --- /dev/null
 +++ b/src/share/classes/java/lang/invoke/InfoFromMemberName.java
-@@ -0,0 +1,135 @@
+@@ -0,0 +1,139 @@
 +/*
 + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
@@ -68,7 +68,7 @@
 +    private final int referenceKind;
 +
 +    InfoFromMemberName(Lookup lookup, MemberName member, byte referenceKind) {
-+        assert(member.isResolved());
++        assert(member.isResolved() || member.isMethodHandleInvoke());
 +        assert(member.referenceKindIsConsistentWith(referenceKind));
 +        this.member = member;
 +        this.referenceKind = referenceKind;
@@ -106,6 +106,10 @@
 +
 +    @Override
 +    public <T extends Member> T reflectAs(Class<T> expected, Lookup lookup) {
++        if (member.isMethodHandleInvoke() && !member.isVarargs()) {
++            // this member is an instance of a signature-polymorphic method, which cannot be reflected
++            throw new IllegalArgumentException("cannot reflect signature polymorphic method");
++        }
 +        Member mem = AccessController.doPrivileged(new PrivilegedAction<Member>() {
 +                public Member run() {
 +                    try {
@@ -160,6 +164,212 @@
 +        }
 +    }
 +}
+diff --git a/src/share/classes/java/lang/invoke/Invokers.java b/src/share/classes/java/lang/invoke/Invokers.java
+--- a/src/share/classes/java/lang/invoke/Invokers.java
++++ b/src/share/classes/java/lang/invoke/Invokers.java
+@@ -87,6 +87,7 @@
+             lform = invokeForm(mtype, true, MethodTypeForm.LF_EX_INVOKER);
+             invoker = SimpleMethodHandle.make(invokerType, lform);
+         }
++        invoker = invoker.withInternalMemberName(MemberName.makeMethodHandleInvoke("invokeExact", mtype));
+         assert(checkInvoker(invoker));
+         exactInvoker = invoker;
+         return invoker;
+@@ -110,6 +111,7 @@
+             lform = invokeForm(mtype, true, MethodTypeForm.LF_GEN_INVOKER);
+             invoker = SimpleMethodHandle.make(invokerType, lform);
+         }
++        invoker = invoker.withInternalMemberName(MemberName.makeMethodHandleInvoke("invoke", mtype));
+         assert(checkInvoker(invoker));
+         generalInvoker = invoker;
+         return invoker;
+diff --git a/src/share/classes/java/lang/invoke/MemberName.java b/src/share/classes/java/lang/invoke/MemberName.java
+--- a/src/share/classes/java/lang/invoke/MemberName.java
++++ b/src/share/classes/java/lang/invoke/MemberName.java
+@@ -320,14 +320,18 @@
+ 
+     /** Utility method to query if this member is a method handle invocation (invoke or invokeExact). */
+     public boolean isMethodHandleInvoke() {
+-        final int bits = Modifier.NATIVE | Modifier.FINAL;
++        final int bits = MH_INVOKE_MODS;
+         final int negs = Modifier.STATIC;
+         if (testFlags(bits | negs, bits) &&
+             clazz == MethodHandle.class) {
+-            return name.equals("invoke") || name.equals("invokeExact");
++            return isMethodHandleInvokeName(name);
+         }
+         return false;
+     }
++    public static boolean isMethodHandleInvokeName(String name) {
++        return name.equals("invoke") || name.equals("invokeExact");
++    }
++    private static final int MH_INVOKE_MODS = Modifier.NATIVE | Modifier.FINAL | Modifier.PUBLIC;
+ 
+     /** Utility method to query the modifier flags of this member. */
+     public boolean isStatic() {
+@@ -482,12 +486,24 @@
+         m.getClass();  // NPE check
+         // fill in vmtarget, vmindex while we have m in hand:
+         MethodHandleNatives.init(this, m);
++        if (clazz == null) {  // MHN.init failed
++            if (m.getDeclaringClass() == MethodHandle.class &&
++                isMethodHandleInvokeName(m.getName())) {
++                MethodType type = MethodType.methodType(m.getReturnType(), m.getParameterTypes());
++                int flags = flagsMods(IS_METHOD, m.getModifiers(), REF_invokeVirtual);
++                init(MethodHandle.class, m.getName(), type, flags);
++                if (isMethodHandleInvoke())
++                    return;
++            }
++            throw new LinkageError(m.toString());
++        }
+         assert(isResolved() && this.clazz != null);
+         this.name = m.getName();
+         if (this.type == null)
+             this.type = new Object[] { m.getReturnType(), m.getParameterTypes() };
+         if (wantSpecial) {
+-            assert(!isAbstract()) : this;
++            if (isAbstract())
++                throw new AbstractMethodError(this.toString());
+             if (getReferenceKind() == REF_invokeVirtual)
+                 changeReferenceKind(REF_invokeSpecial, REF_invokeVirtual);
+             else if (getReferenceKind() == REF_invokeInterface)
+@@ -562,6 +578,17 @@
+         initResolved(true);
+     }
+ 
++    /** Create a name for a signature-polymorphic invoker. */
++    static MemberName makeMethodHandleInvoke(String name, MethodType type) {
++        return makeMethodHandleInvoke(name, type, MH_INVOKE_MODS | SYNTHETIC);
++    }
++    static MemberName makeMethodHandleInvoke(String name, MethodType type, int mods) {
++        MemberName mem = new MemberName(MethodHandle.class, name, type, REF_invokeVirtual);
++        mem.flags |= mods;  // it's not resolved, but add these modifiers anyway
++        assert(mem.isMethodHandleInvoke()) : mem;
++        return mem;
++    }
++
+     // bare-bones constructor; the JVM will fill it in
+     MemberName() { }
+ 
+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
+@@ -1285,6 +1285,11 @@
+     }
+ 
+     /*non-public*/
++    MethodHandle withInternalMemberName(MemberName member) {
++        return MethodHandleImpl.makeWrappedMember(this, member);
++    }
++
++    /*non-public*/
+     boolean isInvokeSpecial() {
+         return false;  // DMH.Special returns true
+     }
+diff --git a/src/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/share/classes/java/lang/invoke/MethodHandleImpl.java
+--- a/src/share/classes/java/lang/invoke/MethodHandleImpl.java
++++ b/src/share/classes/java/lang/invoke/MethodHandleImpl.java
+@@ -777,16 +777,27 @@
+     }
+     static <T extends Throwable> Empty throwException(T t) throws T { throw t; }
+ 
+-    static MethodHandle FAKE_METHOD_HANDLE_INVOKE;
+-    static
+-    MethodHandle fakeMethodHandleInvoke(MemberName method) {
+-        MethodType type = method.getInvocationType();
+-        assert(type.equals(MethodType.methodType(Object.class, Object[].class)));
+-        MethodHandle mh = FAKE_METHOD_HANDLE_INVOKE;
++    static MethodHandle[] FAKE_METHOD_HANDLE_INVOKE = new MethodHandle[2];
++    static MethodHandle fakeMethodHandleInvoke(MemberName method) {
++        int idx;
++        assert(method.isMethodHandleInvoke());
++        switch (method.getName()) {
++        case "invoke":       idx = 0; break;
++        case "invokeExact":  idx = 1; break;
++        default:             throw new InternalError(method.getName());
++        }
++        MethodHandle mh = FAKE_METHOD_HANDLE_INVOKE[idx];
+         if (mh != null)  return mh;
+-        mh = throwException(type.insertParameterTypes(0, UnsupportedOperationException.class));
++        MethodType type = MethodType.methodType(Object.class, UnsupportedOperationException.class,
++                                                MethodHandle.class, Object[].class);
++        mh = throwException(type);
+         mh = mh.bindTo(new UnsupportedOperationException("cannot reflectively invoke MethodHandle"));
+-        FAKE_METHOD_HANDLE_INVOKE = mh;
++        if (!method.getInvocationType().equals(mh.type()))
++            throw new InternalError(method.toString());
++        mh = mh.withInternalMemberName(method);
++        mh = mh.asVarargsCollector(Object[].class);
++        assert(method.isVarargs());
++        FAKE_METHOD_HANDLE_INVOKE[idx] = mh;
+         return mh;
+     }
+ 
+@@ -820,7 +831,7 @@
+             MethodHandle vamh = prepareForInvoker(mh);
+             // Cache the result of makeInjectedInvoker once per argument class.
+             MethodHandle bccInvoker = CV_makeInjectedInvoker.get(hostClass);
+-            return restoreToType(bccInvoker.bindTo(vamh), mh.type());
++            return restoreToType(bccInvoker.bindTo(vamh), mh.type(), mh.internalMemberName());
+         }
+ 
+         private static MethodHandle makeInjectedInvoker(Class<?> hostClass) {
+@@ -875,8 +886,11 @@
+         }
+ 
+         // Undo the adapter effect of prepareForInvoker:
+-        private static MethodHandle restoreToType(MethodHandle vamh, MethodType type) {
+-            return vamh.asCollector(Object[].class, type.parameterCount()).asType(type);
++        private static MethodHandle restoreToType(MethodHandle vamh, MethodType type, MemberName member) {
++            MethodHandle mh = vamh.asCollector(Object[].class, type.parameterCount());
++            mh = mh.asType(type);
++            mh = mh.withInternalMemberName(member);
++            return mh;
+         }
+ 
+         private static final MethodHandle MH_checkCallerClass;
+@@ -938,4 +952,41 @@
+             }
+         }
+     }
++
++
++    /** This subclass allows a wrapped method handle to be re-associated with an arbitrary member name. */
++    static class WrappedMember extends MethodHandle {
++        private final MethodHandle target;
++        private final MemberName member;
++
++        private WrappedMember(MethodHandle target, MethodType type, MemberName member) {
++            super(type, reinvokerForm(type));
++            this.target = target;
++            this.member = member;
++        }
++
++        @Override
++        MethodHandle reinvokerTarget() {
++            return target;
++        }
++        @Override
++        MemberName internalMemberName() {
++            return member;
++        }
++        @Override
++        boolean isInvokeSpecial() {
++            return target.isInvokeSpecial();
++        }
++        @Override
++        MethodHandle viewAsType(MethodType newType) {
++            return new WrappedMember(target, newType, member);
++        }
++    }
++
++    static MethodHandle makeWrappedMember(MethodHandle target, MemberName member) {
++        if (member.equals(target.internalMemberName()))
++            return target;
++        return new WrappedMember(target, target.type(), member);
++    }
++
+ }
 diff --git a/src/share/classes/java/lang/invoke/MethodHandleInfo.java b/src/share/classes/java/lang/invoke/MethodHandleInfo.java
 --- a/src/share/classes/java/lang/invoke/MethodHandleInfo.java
 +++ b/src/share/classes/java/lang/invoke/MethodHandleInfo.java
@@ -497,7 +707,7 @@
  import java.util.List;
  import java.util.ArrayList;
  import java.util.Arrays;
-@@ -51,6 +50,7 @@
+@@ -53,6 +52,7 @@
   * </ul>
   * <p>
   * @author John Rose, JSR 292 EG
@@ -505,7 +715,7 @@
   */
  public class MethodHandles {
  
-@@ -94,6 +94,38 @@
+@@ -96,6 +96,38 @@
      }
  
      /**
@@ -544,7 +754,39 @@
       * A <em>lookup object</em> is a factory for creating method handles,
       * when the creation requires access checking.
       * Method handles do not perform
-@@ -1027,6 +1059,46 @@
+@@ -651,6 +683,7 @@
+                 return invoker(type);
+             if ("invokeExact".equals(name))
+                 return exactInvoker(type);
++            assert(!MemberName.isMethodHandleInvokeName(name));
+             return null;
+         }
+ 
+@@ -896,6 +929,10 @@
+          * @throws NullPointerException if the argument is null
+          */
+         public MethodHandle unreflect(Method m) throws IllegalAccessException {
++            if (m.getDeclaringClass() == MethodHandle.class) {
++                MethodHandle mh = unreflectForMH(m);
++                if (mh != null)  return mh;
++            }
+             MemberName method = new MemberName(m);
+             byte refKind = method.getReferenceKind();
+             if (refKind == REF_invokeSpecial)
+@@ -904,6 +941,12 @@
+             Lookup lookup = m.isAccessible() ? IMPL_LOOKUP : this;
+             return lookup.getDirectMethod(refKind, method.getDeclaringClass(), method, findBoundCallerClass(method));
+         }
++        private MethodHandle unreflectForMH(Method m) {
++            // these names require special lookups because they throw UnsupportedOperationException
++            if (MemberName.isMethodHandleInvokeName(m.getName()))
++                return MethodHandleImpl.fakeMethodHandleInvoke(new MemberName(m));
++            return null;
++        }
+ 
+         /**
+          * Produces a method handle for a reflected method.
+@@ -1008,6 +1051,47 @@
              return unreflectField(f, true);
          }
  
@@ -564,7 +806,7 @@
 +         */
 +        public MethodHandleInfo revealDirect(MethodHandle target) {
 +            MemberName member = target.internalMemberName();
-+            if (member == null || !member.isResolved())
++            if (member == null || (!member.isResolved() && !member.isMethodHandleInvoke()))
 +                throw newIllegalArgumentException("not a direct method handle");
 +            Class<?> defc = member.getDeclaringClass();
 +            byte refKind = member.getReferenceKind();
@@ -579,7 +821,8 @@
 +                refKind = REF_invokeInterface;
 +            // Check SM permissions and member access before cracking.
 +            try {
-+                checkSecurityManager(defc, member, lookupClass());
++                //@@checkSecurityManager(defc, member, lookupClass());
++                checkSecurityManager(defc, member);
 +                checkAccess(member.getReferenceKind(), defc, member);
 +            } catch (IllegalAccessException ex) {
 +                throw new IllegalArgumentException(ex);
@@ -591,7 +834,14 @@
          /// Helper methods, all package-private.
  
          MemberName resolveOrFail(byte refKind, Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
-@@ -1269,6 +1341,7 @@
+@@ -1244,12 +1328,12 @@
+         private MethodHandle getDirectMethodCommon(byte refKind, Class<?> refc, MemberName method,
+                                                    boolean doRestrict, Class<?> callerClass) throws IllegalAccessException {
+             checkMethod(refKind, refc, method);
+-            if (method.isMethodHandleInvoke())
+-                return fakeMethodHandleInvoke(method);
++            assert(!method.isMethodHandleInvoke());
+ 
              Class<?> refcAsSuper;
              if (refKind == REF_invokeSpecial &&
                  refc != lookupClass() &&
@@ -599,38 +849,16 @@
                  refc != (refcAsSuper = lookupClass().getSuperclass()) &&
                  refc.isAssignableFrom(lookupClass())) {
                  assert(!method.getName().equals("<init>"));  // not this code path
-@@ -1297,6 +1370,7 @@
+@@ -1277,9 +1361,6 @@
+                 mh = restrictReceiver(method, mh, lookupClass());
              return mh;
          }
-         private MethodHandle fakeMethodHandleInvoke(MemberName method) {
-+            // FIXME: MethodHandleInfo should be able to crack this.
-             return throwException(method.getReturnType(), UnsupportedOperationException.class);
-         }
+-        private MethodHandle fakeMethodHandleInvoke(MemberName method) {
+-            return throwException(method.getReturnType(), UnsupportedOperationException.class);
+-        }
          private MethodHandle maybeBindCaller(MemberName method, MethodHandle mh,
-@@ -1309,6 +1383,7 @@
-                 hostClass = callerClass;  // callerClass came from a security manager style stack walk
-             MethodHandle cbmh = MethodHandleImpl.bindCaller(mh, hostClass);
-             // Note: caller will apply varargs after this step happens.
-+            // FIXME: MethodHandleInfo should be able to crack this.
-             return cbmh;
-         }
-         private MethodHandle getDirectField(byte refKind, Class<?> refc, MemberName field) throws IllegalAccessException {
-@@ -1474,6 +1549,7 @@
-      */
-     static public
-     MethodHandle exactInvoker(MethodType type) {
-+        // FIXME: MethodHandleInfo should be able to crack this.
-         return type.invokers().exactInvoker();
-     }
- 
-@@ -1507,6 +1583,7 @@
-      */
-     static public
-     MethodHandle invoker(MethodType type) {
-+        // FIXME: MethodHandleInfo should be able to crack this.
-         return type.invokers().generalInvoker();
-     }
- 
+                                              Class<?> callerClass)
+                                              throws IllegalAccessException {
 diff --git a/src/share/classes/java/lang/invoke/SerializedLambda.java b/src/share/classes/java/lang/invoke/SerializedLambda.java
 --- a/src/share/classes/java/lang/invoke/SerializedLambda.java
 +++ b/src/share/classes/java/lang/invoke/SerializedLambda.java
@@ -650,7 +878,7 @@
 new file mode 100644
 --- /dev/null
 +++ b/test/java/lang/invoke/RevealDirectTest.java
-@@ -0,0 +1,478 @@
+@@ -0,0 +1,753 @@
 +/*
 + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
@@ -679,18 +907,18 @@
 +/*
 + * @test
 + * @summary verify Lookup.revealDirect on a variety of input handles
++ * @compile -XDignore.symbol.file RevealDirectTest.java
 + * @run junit/othervm -ea -esa test.java.lang.invoke.RevealDirectTest
-+ */
-+/*
-+ * -ignore The following test appears to fail because of an interaction between JUnit and the SM
-+ * -test
++ *
++ * @test
 + * @summary verify Lookup.revealDirect on a variety of input handles, with security manager
-+ * @run junit/othervm/policy=jtreg.security.policy/secure=java.lang.SecurityManager -ea -esa test.java.lang.invoke.RevealDirectTest
++ * @run main/othervm/policy=jtreg.security.policy/secure=java.lang.SecurityManager -ea -esa test.java.lang.invoke.RevealDirectTest
 + */
 +
 +/* To run manually:
-+ * $ $JAVA8X_HOME/bin/javac -cp ~/env/jars/junit-4.9.jar -d ../../../.. RevealDirectTest.java
-+ * $ $JAVA8X_HOME/bin/java  -cp ~/env/jars/junit-4.9.jar:../../../.. -ea -esa org.junit.runner.JUnitCore test.java.lang.invoke.RevealDirectTest
++ * $ $JAVA8X_HOME/bin/javac -cp $JUNIT4_JAR -d ../../../.. -XDignore.symbol.file RevealDirectTest.java
++ * $ $JAVA8X_HOME/bin/java  -cp $JUNIT4_JAR:../../../.. -ea -esa org.junit.runner.JUnitCore test.java.lang.invoke.RevealDirectTest
++ * $ $JAVA8X_HOME/bin/java  -cp $JUNIT4_JAR:../../../.. -ea -esa    -Djava.security.manager test.java.lang.invoke.RevealDirectTest
 + */
 +
 +package test.java.lang.invoke;
@@ -705,6 +933,28 @@
 +import org.junit.*;
 +
 +public class RevealDirectTest {
++    public static void main(String... av) throws Throwable {
++        // Run the @Test methods explicitly, in case we don't want to use the JUnitCore driver.
++        // This appears to be necessary when running with a security manager.
++        Throwable fail = null;
++        for (Method test : RevealDirectTest.class.getDeclaredMethods()) {
++            if (!test.isAnnotationPresent(Test.class))  continue;
++            try {
++                test.invoke(new RevealDirectTest());
++            } catch (Throwable ex) {
++                if (ex instanceof InvocationTargetException)
++                    ex = ex.getCause();
++                if (fail == null)  fail = ex;
++                System.out.println("Testcase: "+test.getName()
++                                   +"("+test.getDeclaringClass().getName()
++                                   +"):\tCaused an ERROR");
++                System.out.println(ex);
++                ex.printStackTrace(System.out);
++            }
++        }
++        if (fail != null)  throw fail;
++    }
++
 +    public interface SimpleSuperInterface {
 +        public abstract int getInt();
 +        public static void printAll(String... args) {
@@ -713,7 +963,7 @@
 +        public int NICE_CONSTANT = 42;
 +    }
 +    public interface SimpleInterface extends SimpleSuperInterface {
-+        default float getFloat() { return getInt(); } //FIXME: include
++        default float getFloat() { return getInt(); }
 +        public static void printAll(String[] args) {
 +            System.out.println(Arrays.toString(args));
 +        }
@@ -740,26 +990,91 @@
 +
 +    @Test public void testSimple() throws Throwable {
 +        if (VERBOSE)  System.out.println("@Test testSimple");
-+        testOnClass(Simple.localLookup(), null,
-+                    Simple.class, Simple.members());
-+    }
-+    @Test public void testSimpleNegative() throws Throwable {
-+        if (VERBOSE)  System.out.println("@Test testSimpleNegative");
-+        testOnClass(Simple.localLookup(), publicLookup(),
-+                    Simple.class, nonPublicOnly(Simple.members()));
++        testOnMembers("testSimple", Simple.members(), Simple.localLookup());
 +    }
 +    @Test public void testPublicLookup() throws Throwable {
 +        if (VERBOSE)  System.out.println("@Test testPublicLookup");
-+        testOnClass(publicLookup(), null,
-+                    Simple.class, publicOnly(Simple.members()));
++        List<Member> mems = publicOnly(Simple.members());
++        Lookup pubLookup = publicLookup(), privLookup = Simple.localLookup();
++        testOnMembers("testPublicLookup/1", mems, pubLookup);
++        // reveal using publicLookup:
++        testOnMembers("testPublicLookup/2", mems, privLookup, pubLookup);
++        // lookup using publicLookup, but reveal using private:
++        testOnMembers("testPublicLookup/3", mems, pubLookup, privLookup);
 +    }
 +    @Test public void testPublicLookupNegative() throws Throwable {
 +        if (VERBOSE)  System.out.println("@Test testPublicLookupNegative");
-+        testOnClass(publicLookup(), publicLookup(),
-+                    Simple.class, nonPublicOnly(Simple.members()));
++        List<Member> mems = nonPublicOnly(Simple.members());
++        Lookup pubLookup = publicLookup(), privLookup = Simple.localLookup();
++        testOnMembersNoLookup("testPublicLookupNegative/1", mems, pubLookup);
++        testOnMembersNoReveal("testPublicLookupNegative/2", mems, privLookup, pubLookup);
++        testOnMembersNoReflect("testPublicLookupNegative/3", mems, privLookup, pubLookup);
++    }
++    @Test public void testJavaLangClass() throws Throwable {
++        if (VERBOSE)  System.out.println("@Test testJavaLangClass");
++        List<Member> mems = callerSensitive(false, publicOnly(getMembers(Class.class)));
++        mems = limit(20, mems);
++        testOnMembers("testJavaLangClass", mems, Simple.localLookup());
++    }
++    @Test public void testCallerSensitive() throws Throwable {
++        if (VERBOSE)  System.out.println("@Test testCallerSensitive");
++        List<Member> mems = union(getMembers(MethodHandles.class, "lookup"),
++                                  getMembers(Method.class, "invoke"),
++                                  getMembers(Field.class, "get", "set", "getLong"),
++                                  getMembers(Class.class));
++        mems = callerSensitive(true, publicOnly(mems));
++        mems = limit(10, mems);
++        testOnMembers("testCallerSensitive", mems, Simple.localLookup());
++    }
++    @Test public void testCallerSensitiveNegative() throws Throwable {
++        if (VERBOSE)  System.out.println("@Test testCallerSensitiveNegative");
++        List<Member> mems = union(getMembers(MethodHandles.class, "lookup"),
++                                  getMembers(Class.class, "forName"),
++                                  getMembers(Method.class, "invoke"));
++        mems = callerSensitive(true, publicOnly(mems));
++        // CS methods cannot be looked up with publicLookup
++        testOnMembersNoLookup("testCallerSensitiveNegative", mems, publicLookup());
++    }
++    @Test public void testMethodHandleNatives() throws Throwable {
++        if (VERBOSE)  System.out.println("@Test testMethodHandleNatives");
++        List<Member> mems = getMembers(MethodHandle.class, "invoke", "invokeExact");
++        testOnMembers("testMethodHandleNatives", mems, Simple.localLookup());
++    }
++    @Test public void testMethodHandleInvokes() throws Throwable {
++        if (VERBOSE)  System.out.println("@Test testMethodHandleInvokes");
++        List<MethodType> types = new ArrayList<>();
++        Class<?>[] someParamTypes = { void.class, int.class, Object.class, Object[].class };
++        for (Class<?> rt : someParamTypes) {
++            for (Class<?> p0 : someParamTypes) {
++                if (p0 == void.class) { types.add(methodType(rt)); continue; }
++                for (Class<?> p1 : someParamTypes) {
++                    if (p1 == void.class) { types.add(methodType(rt, p0)); continue; }
++                    for (Class<?> p2 : someParamTypes) {
++                        if (p2 == void.class) { types.add(methodType(rt, p0, p1)); continue; }
++                        types.add(methodType(rt, p0, p1, p2));
++                    }
++                }
++            }
++        }
++        List<Member> mems = union(getPolyMembers(MethodHandle.class, "invoke", types),
++                                  getPolyMembers(MethodHandle.class, "invokeExact", types));
++        testOnMembers("testMethodHandleInvokes/1", mems, Simple.localLookup());
++        testOnMembers("testMethodHandleInvokes/2", mems, publicLookup());
 +    }
 +
++    static List<Member> getPolyMembers(Class<?> cls, String name, List<MethodType> types) {
++        assert(cls == MethodHandle.class);
++        ArrayList<Member> mems = new ArrayList<>();
++        for (MethodType type : types) {
++            mems.add(new SignaturePolymorphicMethod(name, type));
++        }
++        return mems;
++    }
 +    static List<Member> getMembers(Class<?> cls) {
++        return getMembers(cls, (String[]) null);
++    }
++    static List<Member> getMembers(Class<?> cls, String... onlyNames) {
++        List<String> names = (onlyNames == null || onlyNames.length == 0 ? null : Arrays.asList(onlyNames));
 +        ArrayList<Member> res = new ArrayList<>();
 +        for (Class<?> sup : getSupers(cls)) {
 +            res.addAll(getDeclaredMembers(sup, "getDeclaredFields"));
@@ -771,11 +1086,12 @@
 +            Member mem = res.get(i);
 +            if (!canBeReached(mem, cls) ||
 +                res.indexOf(mem) != i ||
-+                mem.isSynthetic()) {
++                mem.isSynthetic() ||
++                (names != null && !names.contains(mem.getName()))
++                ) {
 +                res.remove(i--);
 +            }
 +        }
-+        if (VERBOSE)  System.out.println("getMembers => "+res);
 +        return res;
 +    }
 +    static List<Class<?>> getSupers(Class<?> cls) {
@@ -798,18 +1114,20 @@
 +        //System.out.println("getSupers => "+res);
 +        return res;
 +    }
++    static boolean hasSM() {
++        return (System.getSecurityManager() != null);
++    }
 +    static List<Member> getDeclaredMembers(Class<?> cls, String accessor) {
-+        if (VERBOSE)  System.out.println(accessor+" "+cls.getName());
 +        Member[] mems = {};
 +        Method getter = getMethod(Class.class, accessor);
-+        if (System.getSecurityManager() != null) {
++        if (hasSM()) {
 +            try {
 +                mems = (Member[]) invokeMethod(getter, cls);
 +            } catch (SecurityException ex) {
-+                ex.printStackTrace();
++                //if (VERBOSE)  ex.printStackTrace();
 +                accessor = accessor.replace("Declared", "");
 +                getter = getMethod(Class.class, accessor);
-+                System.out.println("replaced accessor: "+getter);
++                if (VERBOSE)  System.out.println("replaced accessor: "+getter);
 +            }
 +        }
 +        if (mems.length == 0) {
@@ -819,6 +1137,7 @@
 +                ex.printStackTrace();
 +            }
 +        }
++        if (VERBOSE)  System.out.println(accessor+" "+cls.getName()+" => "+mems.length+" members");
 +        return Arrays.asList(mems);
 +    }
 +    static Method getMethod(Class<?> cls, String name) {
@@ -841,6 +1160,49 @@
 +        }
 +    }
 +
++    static List<Member> limit(int len, List<Member> mems) {
++        if (mems.size() <= len)  return mems;
++        return mems.subList(0, len);
++    }
++    @SafeVarargs
++    static List<Member> union(List<Member> mems, List<Member>... mem2s) {
++        for (List<Member> mem2 : mem2s) {
++            for (Member m : mem2) {
++                if (!mems.contains(m))
++                    mems.add(m);
++            }
++        }
++        return mems;
++    }
++    static List<Member> callerSensitive(boolean cond, List<Member> members) {
++        for (Iterator<Member> i = members.iterator(); i.hasNext(); ) {
++            Member mem = i.next();
++            if (isCallerSensitive(mem) != cond)
++                i.remove();
++        }
++        if (members.isEmpty())  throw new AssertionError("trivial result");
++        return members;
++    }
++    static boolean isCallerSensitive(Member mem) {
++        if (!(mem instanceof AnnotatedElement))  return false;
++        AnnotatedElement ae = (AnnotatedElement) mem;
++        if (CS_CLASS != null)
++            return ae.isAnnotationPresent(sun.reflect.CallerSensitive.class);
++        for (java.lang.annotation.Annotation a : ae.getDeclaredAnnotations()) {
++            if (a.toString().contains(".CallerSensitive"))
++                return true;
++        }
++        return false;
++    }
++    static final Class<?> CS_CLASS;
++    static {
++        Class<?> c = null;
++        try {
++            c = sun.reflect.CallerSensitive.class;
++        } catch (SecurityException | LinkageError ex) {
++        }
++        CS_CLASS = c;
++    }
 +    static List<Member> publicOnly(List<Member> members) {
 +        return removeMods(members, Modifier.PUBLIC, 0);
 +    }
@@ -862,15 +1224,101 @@
 +        return members;
 +    }
 +
-+    void testOnClass(Lookup lookup, Lookup negLookup,
-+                     Class<?> cls, List<Member> mems) throws Throwable {
++    void testOnMembers(String tname, List<Member> mems, Lookup lookup, Lookup... lookups) throws Throwable {
++        if (VERBOSE)  System.out.println("testOnMembers "+mems);
++        Lookup revLookup = (lookups.length > 0) ? lookups[0] : null;
++        if (revLookup == null)  revLookup = lookup;
++        Lookup refLookup = (lookups.length > 1) ? lookups[1] : null;
++        if (refLookup == null)  refLookup = lookup;
++        assert(lookups.length <= 2);
++        testOnMembersImpl(tname, mems, lookup, revLookup, refLookup, NO_FAIL);
++    }
++    void testOnMembersNoLookup(String tname, List<Member> mems, Lookup lookup) throws Throwable {
++        if (VERBOSE)  System.out.println("testOnMembersNoLookup "+mems);
++        testOnMembersImpl(tname, mems, lookup, null, null, FAIL_LOOKUP);
++    }
++    void testOnMembersNoReveal(String tname, List<Member> mems,
++                               Lookup lookup, Lookup negLookup) throws Throwable {
++        if (VERBOSE)  System.out.println("testOnMembersNoReveal "+mems);
++        testOnMembersImpl(tname, mems, lookup, negLookup, null, FAIL_REVEAL);
++    }
++    void testOnMembersNoReflect(String tname, List<Member> mems,
++                                Lookup lookup, Lookup negLookup) throws Throwable {
++        if (VERBOSE)  System.out.println("testOnMembersNoReflect "+mems);
++        testOnMembersImpl(tname, mems, lookup, lookup, negLookup, FAIL_REFLECT);
++    }
++    void testOnMembersImpl(String tname, List<Member> mems,
++                           Lookup lookup,
++                           Lookup revLookup,
++                           Lookup refLookup,
++                           int failureMode) throws Throwable {
++        Throwable fail = null;
++        int failCount = 0;
++        failureModeCounts = new int[FAIL_MODE_COUNT];
++        long tm0 = System.currentTimeMillis();
 +        for (Member mem : mems) {
-+            testWithMember(lookup, negLookup, mem);
++            try {
++                testWithMember(mem, lookup, revLookup, refLookup, failureMode);
++            } catch (Throwable ex) {
++                if (fail == null)  fail = ex;
++                if (++failCount > 10) { System.out.println("*** FAIL: too many failures"); break; }
++                System.out.println("*** FAIL: "+mem+" => "+ex);
++                if (VERBOSE)  ex.printStackTrace(System.out);
++            }
 +        }
++        long tm1 = System.currentTimeMillis();
++        System.out.printf("@Test %s executed %s tests in %d ms",
++                          tname, testKinds(failureModeCounts), (tm1-tm0)).println();
++        if (fail != null)  throw fail;
++    }
++    static String testKinds(int[] modes) {
++        int pos = modes[0], neg = -pos;
++        for (int n : modes)  neg += n;
++        if (neg == 0)  return pos + " positive";
++        String negs = "";
++        for (int n : modes)  negs += "/"+n;
++        negs = negs.replaceFirst("/"+pos+"/", "");
++        negs += " negative";
++        if (pos == 0)  return negs;
++        return pos + " positive, " + negs;
++    }
++    static class SignaturePolymorphicMethod implements Member {  // non-reflected instance of MH.invoke*
++        final String name;
++        final MethodType type;
++        SignaturePolymorphicMethod(String name, MethodType type) {
++            this.name = name;
++            this.type = type;
++        }
++        public String toString() {
++            String typeStr = type.toString();
++            if (isVarArgs())  typeStr = typeStr.replaceFirst("\\[\\])$", "...)");
++            return (Modifier.toString(getModifiers())
++                    +typeStr.substring(0, typeStr.indexOf('('))+" "
++                    +getDeclaringClass().getTypeName()+"."
++                    +getName()+typeStr.substring(typeStr.indexOf('(')));
++        }
++        public boolean equals(Object x) {
++            return (x instanceof SignaturePolymorphicMethod && equals((SignaturePolymorphicMethod)x));
++        }
++        public boolean equals(SignaturePolymorphicMethod that) {
++            return this.name.equals(that.name) && this.type.equals(that.type);
++        }
++        public int hashCode() {
++            return name.hashCode() * 31 + type.hashCode();
++        }
++        public Class<?> getDeclaringClass() { return MethodHandle.class; }
++        public String getName() { return name; }
++        public MethodType getMethodType() { return type; }
++        public int getModifiers() { return Modifier.PUBLIC | Modifier.FINAL | Modifier.NATIVE | SYNTHETIC; }
++        public boolean isVarArgs() { return Modifier.isTransient(getModifiers()); }
++        public boolean isSynthetic() { return true; }
++        public Class<?> getReturnType() { return type.returnType(); }
++        public Class<?>[] getParameterTypes() { return type.parameterArray(); }
++        static final int SYNTHETIC = 0x00001000;
 +    }
 +    static class UnreflectResult {  // a tuple
 +        final MethodHandle mh;
-+        final ReflectiveOperationException ex;
++        final Throwable ex;
 +        final byte kind;
 +        final Member mem;
 +        final int var;
@@ -881,7 +1329,7 @@
 +            this.mem = mem;
 +            this.var = var;
 +        }
-+        UnreflectResult(ReflectiveOperationException ex, byte kind, Member mem, int var) {
++        UnreflectResult(Throwable ex, byte kind, Member mem, int var) {
 +            this.mh = null;
 +            this.ex = ex;
 +            this.kind = kind;
@@ -889,6 +1337,9 @@
 +            this.var = var;
 +        }
 +        public String toString() {
++            return toInfoString()+"/v"+var;
++        }
++        public String toInfoString() {
 +            return String.format("%s %s.%s:%s", MethodHandleInfo.referenceKindToString(kind),
 +                                 mem.getDeclaringClass().getName(), name(mem), type(mem, kind));
 +        }
@@ -902,6 +1353,8 @@
 +                if (kind == REF_putStatic || kind == REF_putField)
 +                    return methodType(void.class, type);
 +                return methodType(type);
++            } else if (mem instanceof SignaturePolymorphicMethod) {
++                return ((SignaturePolymorphicMethod)mem).getMethodType();
 +            }
 +            Class<?>[] params = ((Executable)mem).getParameterTypes();
 +            if (mem instanceof Constructor)
@@ -914,7 +1367,7 @@
 +        byte[] refKind = {0};
 +        try {
 +            return unreflectMemberOrThrow(lookup, mem, variation, refKind);
-+        } catch (ReflectiveOperationException ex) {
++        } catch (ReflectiveOperationException|SecurityException ex) {
 +            return new UnreflectResult(ex, refKind[0], mem, variation);
 +        }
 +    }
@@ -945,23 +1398,40 @@
 +            refKind[0] = kind;
 +            switch (variation) {
 +            case 0:
++                mh = lookup.unreflect(m);
++                break;
++            case 1:
++                if (defc == MethodHandle.class &&
++                    !isStatic &&
++                    m.isVarArgs() &&
++                    Modifier.isFinal(mods) &&
++                    Modifier.isNative(mods)) {
++                    break;
++                }
 +                if (isStatic)
 +                    mh = lookup.findStatic(defc, name, type);
 +                else
 +                    mh = lookup.findVirtual(defc, name, type);
 +                break;
-+            case 1:
-+                mh = lookup.unreflect(m);
++            case 2:
++                if (!canBeSpecial)
++                    break;
++                mh = lookup.unreflectSpecial(m, lookup.lookupClass());
 +                break;
-+            case 2:
++            case 3:
 +                if (!canBeSpecial)
 +                    break;
 +                mh = lookup.findSpecial(defc, name, type, lookup.lookupClass());
 +                break;
-+            case 3:
-+                if (!canBeSpecial)
-+                    break;
-+                mh = lookup.unreflectSpecial(m, lookup.lookupClass());
++            }
++        } else if (mem instanceof SignaturePolymorphicMethod) {
++            SignaturePolymorphicMethod m = (SignaturePolymorphicMethod) mem;
++            MethodType type = methodType(m.getReturnType(), m.getParameterTypes());
++            kind = REF_invokeVirtual;
++            refKind[0] = kind;
++            switch (variation) {
++            case 0:
++                mh = lookup.findVirtual(defc, name, type);
 +                break;
 +            }
 +        } else if (mem instanceof Constructor) {
@@ -972,10 +1442,10 @@
 +            refKind[0] = kind;
 +            switch (variation) {
 +            case 0:
-+                mh = lookup.findConstructor(defc, type);
++                mh = lookup.unreflectConstructor(m);
 +                break;
 +            case 1:
-+                mh = lookup.unreflectConstructor(m);
++                mh = lookup.findConstructor(defc, type);
 +                break;
 +            }
 +        } else if (mem instanceof Field) {
@@ -989,13 +1459,18 @@
 +            refKind[0] = kind;
 +            switch (variation) {
 +            case 0:
++                mh = lookup.unreflectGetter(m);
++                break;
++            case 1:
 +                if (isStatic)
 +                    mh = lookup.findStaticGetter(defc, name, type);
 +                else
 +                    mh = lookup.findGetter(defc, name, type);
 +                break;
-+            case 1:
-+                mh = lookup.unreflectGetter(m);
++            case 3:
++                if (!canHaveSetter)
++                    break;
++                mh = lookup.unreflectSetter(m);
 +                break;
 +            case 2:
 +                if (!canHaveSetter)
@@ -1005,11 +1480,6 @@
 +                else
 +                    mh = lookup.findSetter(defc, name, type);
 +                break;
-+            case 3:
-+                if (!canHaveSetter)
-+                    break;
-+                mh = lookup.unreflectSetter(m);
-+                break;
 +            }
 +        } else {
 +            throw new IllegalArgumentException(String.valueOf(mem));
@@ -1056,8 +1526,8 @@
 +            expectType = expectType.changeReturnType(void.class);
 +        assertEquals(expectType, info.getMethodType());
 +        assertEquals(res.mh.isVarargsCollector(), isVarArgs(info));
-+        assertEquals(res.toString(), info.toString());
-+        assertEquals(res.toString(), MethodHandleInfo.toString(info.getReferenceKind(), info.getDeclaringClass(), info.getName(), info.getMethodType()));
++        assertEquals(res.toInfoString(), info.toString());
++        assertEquals(res.toInfoString(), MethodHandleInfo.toString(info.getReferenceKind(), info.getDeclaringClass(), info.getName(), info.getMethodType()));
 +        return true;
 +    }
 +    static boolean isVarArgs(MethodHandleInfo info) {
@@ -1081,37 +1551,62 @@
 +        assertEquals(mh.isVarargsCollector(), mh2.isVarargsCollector());
 +        return true;
 +    }
-+    void testWithMember(Lookup lookup, Lookup negLookup, Member mem) throws Throwable {
-+        boolean hasSM = (System.getSecurityManager() != null);
-+        boolean expectEx = (negLookup == lookup);
-+        Lookup refLookup = (negLookup != null ? negLookup : lookup);
-+        boolean expectEx2 = (refLookup == negLookup);
++    int[] failureModeCounts;
++    static final int NO_FAIL=0, FAIL_LOOKUP=1, FAIL_REVEAL=2, FAIL_REFLECT=3, FAIL_MODE_COUNT=4;
++    void testWithMember(Member mem,
++                        Lookup lookup,      // initial lookup of member => MH
++                        Lookup revLookup,   // reveal MH => info
++                        Lookup refLookup,   // reflect info => member
++                        int failureMode) throws Throwable {
++        boolean expectEx1 = (failureMode == FAIL_LOOKUP);   // testOnMembersNoLookup
++        boolean expectEx2 = (failureMode == FAIL_REVEAL);   // testOnMembersNoReveal
++        boolean expectEx3 = (failureMode == FAIL_REFLECT);  // testOnMembersNoReflect
 +        for (int variation = 0; ; variation++) {
 +            UnreflectResult res = unreflectMember(lookup, mem, variation);
++            failureModeCounts[failureMode] += 1;
++            if (variation == 0)  assert(res != null);
 +            if (res == null)  break;
 +            if (VERBOSE && variation == 0)
 +                System.out.println("from "+mem.getDeclaringClass().getSimpleName());
 +            MethodHandle mh = res.mh;
-+            Throwable ex = res.ex;
-+            if (VERBOSE)  System.out.println("  "+variation+": "+res+"  << "+(mh != null ? mh : ex));
-+            if (expectEx && ex != null)
++            Throwable   ex1 = res.ex;
++            if (VERBOSE)  System.out.println("  "+variation+": "+res+"  << "+(mh != null ? mh : ex1));
++            if (expectEx1 && ex1 != null)
 +                continue;  // this is OK; we expected that lookup to fail
-+            if (expectEx)
-+                throw new AssertionError("lookup succeeded unexpectedly for negative test");
-+            if (ex != null && !expectEx) {
-+                if (negLookup == null)  throw ex;
-+                throw new AssertionError("lookup failed unexpectedly for negative test", ex);
++            if (expectEx1)
++                throw new AssertionError("unexpected lookup for negative test");
++            if (ex1 != null && !expectEx1) {
++                if (failureMode != NO_FAIL)
++                    throw new AssertionError("unexpected lookup failure for negative test", ex1);
++                throw ex1;
 +            }
-+            MethodHandleInfo info = lookup.revealDirect(mh);
++            MethodHandleInfo info;
++            try {
++                info = revLookup.revealDirect(mh);
++                if (expectEx2)  throw new AssertionError("unexpected revelation for negative test");
++            } catch (Throwable ex2) {
++                if (VERBOSE)  System.out.println("  "+variation+": "+res+" => "+mh.getClass().getName()+" => (EX2)"+ex2);
++                if (expectEx2)
++                    continue;  // this is OK; we expected the reflect to fail
++                if (failureMode != NO_FAIL)
++                    throw new AssertionError("unexpected revelation failure for negative test", ex2);
++                throw ex2;
++            }
 +            assert(consistent(res, info));
 +            Member mem2;
 +            try {
 +                mem2 = info.reflectAs(Member.class, refLookup);
-+                if (expectEx2)  throw new AssertionError("reflection succeeded unexpectedly for negative test");
-+            } catch (IllegalArgumentException ex2) {
-+                if (VERBOSE)  System.out.println("  "+variation+": "+info+"  (REFLECT)<< "+ex2);
-+                if (expectEx2)  continue;  // this is OK; we expected the reflect to fail
-+                throw ex2;
++                if (expectEx3)  throw new AssertionError("unexpected reflection for negative test");
++                assert(!(mem instanceof SignaturePolymorphicMethod));
++            } catch (IllegalArgumentException ex3) {
++                if (VERBOSE)  System.out.println("  "+variation+": "+info+" => (EX3)"+ex3);
++                if (expectEx3)
++                    continue;  // this is OK; we expected the reflect to fail
++                if (mem instanceof SignaturePolymorphicMethod)
++                    continue;  // this is OK; we cannot reflect MH.invokeExact(a,b,c)
++                if (failureMode != NO_FAIL)
++                    throw new AssertionError("unexpected reflection failure for negative test", ex3);
++                throw ex3;
 +            }
 +            assert(consistent(mem, mem2));
 +            UnreflectResult res2 = unreflectMember(lookup, mem2, variation);
@@ -1121,11 +1616,19 @@
 +            assert(consistent(info, info2));
 +            assert(consistent(res, info2));
 +            Member mem3;
-+            if (hasSM)
++            if (hasSM())
 +                mem3 = info2.reflectAs(Member.class, lookup);
 +            else
 +                mem3 = MethodHandles.reflectAs(Member.class, mh2);
 +            assert(consistent(mem2, mem3));
++            if (hasSM()) {
++                try {
++                    MethodHandles.reflectAs(Member.class, mh2);
++                    throw new AssertionError("failed to throw on "+mem3);
++                } catch (SecurityException ex3) {
++                    // OK...
++                }
++            }
 +        }
 +    }
 +}