8008688: Make MethodHandleInfo public
Summary: A major overhaul to MethodHandleInfo and method handles in general.
Reviewed-by: vlivanov, twisti
Contributed-by: john.r.rose@oracle.com
diff --git a/test/java/lang/invoke/RevealDirectTest.java b/test/java/lang/invoke/RevealDirectTest.java
new file mode 100644
index 0000000..f05b190
--- /dev/null
+++ b/test/java/lang/invoke/RevealDirectTest.java
@@ -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.
+ *
+ * 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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
+ * @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
+ *
+ * @test
+ * @summary verify Lookup.revealDirect on a variety of input handles, with security manager
+ * @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 $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;
+
+import java.lang.reflect.*;
+import java.lang.invoke.*;
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+import static java.lang.invoke.MethodHandleInfo.*;
+import java.util.*;
+import static org.junit.Assert.*;
+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) {
+            System.out.println(Arrays.toString(args));
+        }
+        public int NICE_CONSTANT = 42;
+    }
+    public interface SimpleInterface extends SimpleSuperInterface {
+        default float getFloat() { return getInt(); }
+        public static void printAll(String[] args) {
+            System.out.println(Arrays.toString(args));
+        }
+    }
+    public static class Simple implements SimpleInterface, Cloneable {
+        public int intField;
+        public final int finalField;
+        private static String stringField;
+        public int getInt() { return NICE_CONSTANT; }
+        private static Number getNum() { return 804; }
+        public Simple clone() {
+            try {
+                return (Simple) super.clone();
+            } catch (CloneNotSupportedException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+        Simple() { finalField = -NICE_CONSTANT; }
+        private static Lookup localLookup() { return lookup(); }
+        private static List<Member> members() { return getMembers(lookup().lookupClass()); };
+    }
+
+    static boolean VERBOSE = false;
+
+    @Test public void testSimple() throws Throwable {
+        if (VERBOSE)  System.out.println("@Test testSimple");
+        testOnMembers("testSimple", Simple.members(), Simple.localLookup());
+    }
+    @Test public void testPublicLookup() throws Throwable {
+        if (VERBOSE)  System.out.println("@Test testPublicLookup");
+        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");
+        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"));
+            res.addAll(getDeclaredMembers(sup, "getDeclaredMethods"));
+            res.addAll(getDeclaredMembers(sup, "getDeclaredConstructors"));
+        }
+        res = new ArrayList<>(new LinkedHashSet<>(res));
+        for (int i = 0; i < res.size(); i++) {
+            Member mem = res.get(i);
+            if (!canBeReached(mem, cls) ||
+                res.indexOf(mem) != i ||
+                mem.isSynthetic() ||
+                (names != null && !names.contains(mem.getName()))
+                ) {
+                res.remove(i--);
+            }
+        }
+        return res;
+    }
+    static List<Class<?>> getSupers(Class<?> cls) {
+        ArrayList<Class<?>> res = new ArrayList<>();
+        ArrayList<Class<?>> intfs = new ArrayList<>();
+        for (Class<?> sup = cls; sup != null; sup = sup.getSuperclass()) {
+            res.add(sup);
+            for (Class<?> intf : cls.getInterfaces()) {
+                if (!intfs.contains(intf))
+                    intfs.add(intf);
+            }
+        }
+        for (int i = 0; i < intfs.size(); i++) {
+            for (Class<?> intf : intfs.get(i).getInterfaces()) {
+                if (!intfs.contains(intf))
+                    intfs.add(intf);
+            }
+        }
+        res.addAll(intfs);
+        //System.out.println("getSupers => "+res);
+        return res;
+    }
+    static boolean hasSM() {
+        return (System.getSecurityManager() != null);
+    }
+    static List<Member> getDeclaredMembers(Class<?> cls, String accessor) {
+        Member[] mems = {};
+        Method getter = getMethod(Class.class, accessor);
+        if (hasSM()) {
+            try {
+                mems = (Member[]) invokeMethod(getter, cls);
+            } catch (SecurityException ex) {
+                //if (VERBOSE)  ex.printStackTrace();
+                accessor = accessor.replace("Declared", "");
+                getter = getMethod(Class.class, accessor);
+                if (VERBOSE)  System.out.println("replaced accessor: "+getter);
+            }
+        }
+        if (mems.length == 0) {
+            try {
+                mems = (Member[]) invokeMethod(getter, cls);
+            } catch (SecurityException ex) {
+                ex.printStackTrace();
+            }
+        }
+        if (VERBOSE)  System.out.println(accessor+" "+cls.getName()+" => "+mems.length+" members");
+        return Arrays.asList(mems);
+    }
+    static Method getMethod(Class<?> cls, String name) {
+        try {
+            return cls.getMethod(name);
+        } catch (ReflectiveOperationException ex) {
+            throw new AssertionError(ex);
+        }
+    }
+    static Object invokeMethod(Method m, Object recv, Object... args) {
+        try {
+            return m.invoke(recv, args);
+        } catch (InvocationTargetException ex) {
+            Throwable ex2 = ex.getCause();
+            if (ex2 instanceof RuntimeException)  throw (RuntimeException) ex2;
+            if (ex2 instanceof Error)  throw (Error) ex2;
+            throw new AssertionError(ex);
+        } catch (ReflectiveOperationException ex) {
+            throw new AssertionError(ex);
+        }
+    }
+
+    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);
+    }
+    static List<Member> nonPublicOnly(List<Member> members) {
+        return removeMods(members, Modifier.PUBLIC, -1);
+    }
+    static List<Member> removeMods(List<Member> members, int mask, int bits) {
+        int publicMods = (mask & Modifier.PUBLIC);
+        members = new ArrayList<>(members);
+        for (Iterator<Member> i = members.iterator(); i.hasNext(); ) {
+            Member mem = i.next();
+            int mods = mem.getModifiers();
+            if ((publicMods & mods) != 0 &&
+                (publicMods & mem.getDeclaringClass().getModifiers()) == 0)
+                mods -= publicMods;
+            if ((mods & mask) == (bits & mask))
+                i.remove();
+        }
+        return members;
+    }
+
+    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) {
+            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 Throwable ex;
+        final byte kind;
+        final Member mem;
+        final int var;
+        UnreflectResult(MethodHandle mh, byte kind, Member mem, int var) {
+            this.mh = mh;
+            this.ex = null;
+            this.kind = kind;
+            this.mem = mem;
+            this.var = var;
+        }
+        UnreflectResult(Throwable ex, byte kind, Member mem, int var) {
+            this.mh = null;
+            this.ex = ex;
+            this.kind = kind;
+            this.mem = mem;
+            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));
+        }
+        static String name(Member mem) {
+            if (mem instanceof Constructor)  return "<init>";
+            return mem.getName();
+        }
+        static MethodType type(Member mem, byte kind) {
+            if (mem instanceof Field) {
+                Class<?> type = ((Field)mem).getType();
+                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)
+                return methodType(void.class, params);
+            Class<?> type = ((Method)mem).getReturnType();
+            return methodType(type, params);
+        }
+    }
+    static UnreflectResult unreflectMember(Lookup lookup, Member mem, int variation) {
+        byte[] refKind = {0};
+        try {
+            return unreflectMemberOrThrow(lookup, mem, variation, refKind);
+        } catch (ReflectiveOperationException|SecurityException ex) {
+            return new UnreflectResult(ex, refKind[0], mem, variation);
+        }
+    }
+    static UnreflectResult unreflectMemberOrThrow(Lookup lookup, Member mem, int variation,
+                                                  byte[] refKind) throws ReflectiveOperationException {
+        Class<?> cls = lookup.lookupClass();
+        Class<?> defc = mem.getDeclaringClass();
+        String   name = mem.getName();
+        int      mods = mem.getModifiers();
+        boolean isStatic = Modifier.isStatic(mods);
+        MethodHandle mh = null;
+        byte kind = 0;
+        if (mem instanceof Method) {
+            Method m = (Method) mem;
+            MethodType type = methodType(m.getReturnType(), m.getParameterTypes());
+            boolean canBeSpecial = (!isStatic &&
+                                    (lookup.lookupModes() & Modifier.PRIVATE) != 0 &&
+                                    defc.isAssignableFrom(cls) &&
+                                    (!defc.isInterface() || Arrays.asList(cls.getInterfaces()).contains(defc)));
+            if (variation >= 2)
+                kind = REF_invokeSpecial;
+            else if (isStatic)
+                kind = REF_invokeStatic;
+            else if (defc.isInterface())
+                kind = REF_invokeInterface;
+            else
+                kind = REF_invokeVirtual;
+            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 2:
+                if (!canBeSpecial)
+                    break;
+                mh = lookup.unreflectSpecial(m, lookup.lookupClass());
+                break;
+            case 3:
+                if (!canBeSpecial)
+                    break;
+                mh = lookup.findSpecial(defc, name, type, lookup.lookupClass());
+                break;
+            }
+        } 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) {
+            name = "<init>";  // not used
+            Constructor<?> m = (Constructor<?>) mem;
+            MethodType type = methodType(void.class, m.getParameterTypes());
+            kind = REF_newInvokeSpecial;
+            refKind[0] = kind;
+            switch (variation) {
+            case 0:
+                mh = lookup.unreflectConstructor(m);
+                break;
+            case 1:
+                mh = lookup.findConstructor(defc, type);
+                break;
+            }
+        } else if (mem instanceof Field) {
+            Field m = (Field) mem;
+            Class<?> type = m.getType();
+            boolean canHaveSetter = !Modifier.isFinal(mods);
+            if (variation >= 2)
+                kind = (byte)(isStatic ? REF_putStatic : REF_putField);
+            else
+                kind = (byte)(isStatic ? REF_getStatic : REF_getField);
+            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 3:
+                if (!canHaveSetter)
+                    break;
+                mh = lookup.unreflectSetter(m);
+                break;
+            case 2:
+                if (!canHaveSetter)
+                    break;
+                if (isStatic)
+                    mh = lookup.findStaticSetter(defc, name, type);
+                else
+                    mh = lookup.findSetter(defc, name, type);
+                break;
+            }
+        } else {
+            throw new IllegalArgumentException(String.valueOf(mem));
+        }
+        if (mh == null)
+            // ran out of valid variations; return null to caller
+            return null;
+        return new UnreflectResult(mh, kind, mem, variation);
+    }
+    static boolean canBeReached(Member mem, Class<?> cls) {
+        Class<?> defc = mem.getDeclaringClass();
+        String   name = mem.getName();
+        int      mods = mem.getModifiers();
+        if (mem instanceof Constructor) {
+            name = "<init>";  // according to 292 spec.
+        }
+        if (defc == cls)
+            return true;
+        if (name.startsWith("<"))
+            return false;  // only my own constructors
+        if (Modifier.isPrivate(mods))
+            return false;  // only my own constructors
+        if (defc.getPackage() == cls.getPackage())
+            return true;   // package access or greater OK
+        if (Modifier.isPublic(mods))
+            return true;   // publics always OK
+        if (Modifier.isProtected(mods) && defc.isAssignableFrom(cls))
+            return true;   // protected OK
+        return false;
+    }
+    static boolean consistent(UnreflectResult res, MethodHandleInfo info) {
+        assert(res.mh != null);
+        assertEquals(res.kind, info.getReferenceKind());
+        assertEquals(res.mem.getModifiers(), info.getModifiers());
+        assertEquals(res.mem.getDeclaringClass(), info.getDeclaringClass());
+        String expectName = res.mem.getName();
+        if (res.kind == REF_newInvokeSpecial)
+            expectName = "<init>";
+        assertEquals(expectName, info.getName());
+        MethodType expectType = res.mh.type();
+        if ((res.kind & 1) == (REF_getField & 1))
+            expectType = expectType.dropParameterTypes(0, 1);
+        if (res.kind == REF_newInvokeSpecial)
+            expectType = expectType.changeReturnType(void.class);
+        assertEquals(expectType, info.getMethodType());
+        assertEquals(res.mh.isVarargsCollector(), isVarArgs(info));
+        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) {
+        return info.isVarArgs();
+    }
+    static boolean consistent(Member mem, Member mem2) {
+        assertEquals(mem, mem2);
+        return true;
+    }
+    static boolean consistent(MethodHandleInfo info, MethodHandleInfo info2) {
+        assertEquals(info.getReferenceKind(), info2.getReferenceKind());
+        assertEquals(info.getModifiers(), info2.getModifiers());
+        assertEquals(info.getDeclaringClass(), info2.getDeclaringClass());
+        assertEquals(info.getName(), info2.getName());
+        assertEquals(info.getMethodType(), info2.getMethodType());
+        assertEquals(isVarArgs(info), isVarArgs(info));
+        return true;
+    }
+    static boolean consistent(MethodHandle mh, MethodHandle mh2) {
+        assertEquals(mh.type(), mh2.type());
+        assertEquals(mh.isVarargsCollector(), mh2.isVarargsCollector());
+        return true;
+    }
+    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   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 (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;
+            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 (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);
+            MethodHandle mh2 = res2.mh;
+            assert(consistent(mh, mh2));
+            MethodHandleInfo info2 = lookup.revealDirect(mh2);
+            assert(consistent(info, info2));
+            assert(consistent(res, info2));
+            Member mem3;
+            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...
+                }
+            }
+        }
+    }
+}