| /* | |
| * Copyright (c) 2013, 2024, 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 | |
| * @summary verify Lookup.revealDirect on a variety of input handles | |
| * @modules java.base/jdk.internal.reflect | |
| * @compile -XDignore.symbol.file RevealDirectTest.java | |
| * @run junit/othervm -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 | |
| */ | |
| 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 class Nestmate { | |
| private static Lookup localLookup() { return lookup(); } | |
| } | |
| 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/1", mems, publicLookup()); | |
| // CS methods have to be revealed with a matching lookupClass | |
| testOnMembersNoReveal("testCallerSensitiveNegative/2", mems, Simple.localLookup(), publicLookup()); | |
| testOnMembersNoReveal("testCallerSensitiveNegative/3", mems, Simple.localLookup(), Nestmate.localLookup()); | |
| // CS methods have to have original access | |
| Lookup lookup = Simple.localLookup().dropLookupMode(Lookup.ORIGINAL); | |
| testOnMembersNoLookup("testCallerSensitiveNegative/4", mems, lookup); | |
| testOnMembersNoReveal("testCallerSensitiveNegative/5", mems, Simple.localLookup(), lookup); | |
| } | |
| @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 List<Member> getDeclaredMembers(Class<?> cls, String accessor) { | |
| Member[] mems = {}; | |
| Method getter = getMethod(Class.class, accessor); | |
| 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(jdk.internal.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 = jdk.internal.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 (IllegalArgumentException|SecurityException 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 = MethodHandles.reflectAs(Member.class, mh2); | |
| assert(consistent(mem2, mem3)); | |
| } | |
| } | |
| } |