/* * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* @test * @bug 8196830 8235351 * @modules java.base/jdk.internal.reflect * @run testng/othervm --illegal-access=deny CallerSensitiveAccess * @summary Check Lookup findVirtual, findStatic and unreflect behavior with * caller sensitive methods with focus on AccessibleObject.setAccessible */ import java.io.IOException; import java.io.UncheckedIOException; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; import java.lang.module.ModuleReader; import java.lang.module.ModuleReference; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.List; import java.util.StringJoiner; import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.internal.reflect.CallerSensitive; import org.testng.annotations.DataProvider; import org.testng.annotations.NoInjection; import org.testng.annotations.Test; import static org.testng.Assert.*; public class CallerSensitiveAccess { /** * Caller sensitive methods in APIs exported by java.base. */ @DataProvider(name = "callerSensitiveMethods") static Object[][] callerSensitiveMethods() { return callerSensitiveMethods(Object.class.getModule()) .map(m -> new Object[] { m, shortDescription(m) }) .toArray(Object[][]::new); } /** * Using publicLookup, attempt to use findVirtual or findStatic to obtain a * method handle to a caller sensitive method. */ @Test(dataProvider = "callerSensitiveMethods", expectedExceptions = IllegalAccessException.class) public void testPublicLookupFind(@NoInjection Method method, String desc) throws Exception { Lookup lookup = MethodHandles.publicLookup(); Class refc = method.getDeclaringClass(); String name = method.getName(); MethodType mt = MethodType.methodType(method.getReturnType(), method.getParameterTypes()); if (Modifier.isStatic(method.getModifiers())) { lookup.findStatic(refc, name, mt); } else { lookup.findVirtual(refc, name, mt); } } /** * Using publicLookup, attempt to use unreflect to obtain a method handle to a * caller sensitive method. */ @Test(dataProvider = "callerSensitiveMethods", expectedExceptions = IllegalAccessException.class) public void testPublicLookupUnreflect(@NoInjection Method method, String desc) throws Exception { MethodHandles.publicLookup().unreflect(method); } /** * public accessible caller sensitive methods in APIs exported by java.base. */ @DataProvider(name = "accessibleCallerSensitiveMethods") static Object[][] accessibleCallerSensitiveMethods() { return callerSensitiveMethods(Object.class.getModule()) .filter(m -> Modifier.isPublic(m.getModifiers())) .map(m -> { m.setAccessible(true); return m; }) .map(m -> new Object[] { m, shortDescription(m) }) .toArray(Object[][]::new); } /** * Using publicLookup, attempt to use unreflect to obtain a method handle to a * caller sensitive method. */ @Test(dataProvider = "accessibleCallerSensitiveMethods", expectedExceptions = IllegalAccessException.class) public void testLookupUnreflect(@NoInjection Method method, String desc) throws Exception { MethodHandles.publicLookup().unreflect(method); } // -- Test method handles to setAccessible -- private int aField; Field accessibleField() { try { return getClass().getDeclaredField("aField"); } catch (NoSuchFieldException e) { fail(); return null; } } Field inaccessibleField() { try { return String.class.getDeclaredField("hash"); } catch (NoSuchFieldException e) { fail(); return null; } } void findAndInvokeSetAccessible(Class refc, Field f) throws Throwable { MethodType mt = MethodType.methodType(void.class, boolean.class); MethodHandle mh = MethodHandles.lookup().findVirtual(refc, "setAccessible", mt); mh.invoke(f, true); // may throw InaccessibleObjectException assertTrue(f.isAccessible()); } void unreflectAndInvokeSetAccessible(Method m, Field f) throws Throwable { assertTrue(m.getName().equals("setAccessible")); assertFalse(Modifier.isStatic(m.getModifiers())); MethodHandle mh = MethodHandles.lookup().unreflect(m); mh.invoke(f, true); // may throw InaccessibleObjectException assertTrue(f.isAccessible()); } /** * Create a method handle to setAccessible and use it to suppress access to an * accessible member. */ @Test public void testSetAccessible1() throws Throwable { findAndInvokeSetAccessible(AccessibleObject.class, accessibleField()); } @Test public void testSetAccessible2() throws Throwable { findAndInvokeSetAccessible(Field.class, accessibleField()); } @Test public void testSetAccessible3() throws Throwable { Method m = AccessibleObject.class.getMethod("setAccessible", boolean.class); unreflectAndInvokeSetAccessible(m, accessibleField()); } @Test public void testSetAccessible4() throws Throwable { Method m = Field.class.getMethod("setAccessible", boolean.class); unreflectAndInvokeSetAccessible(m, accessibleField()); } /** * Create a method handle to setAccessible and attempt to use it to suppress * access to an inaccessible member. */ @Test(expectedExceptions = InaccessibleObjectException.class) public void testSetAccessible5() throws Throwable { findAndInvokeSetAccessible(AccessibleObject.class, inaccessibleField()); } @Test(expectedExceptions = InaccessibleObjectException.class) public void testSetAccessible6() throws Throwable { findAndInvokeSetAccessible(Field.class, inaccessibleField()); } @Test(expectedExceptions = InaccessibleObjectException.class) public void testSetAccessible7() throws Throwable { Method m = AccessibleObject.class.getMethod("setAccessible", boolean.class); unreflectAndInvokeSetAccessible(m, inaccessibleField()); } @Test(expectedExceptions = InaccessibleObjectException.class) public void testSetAccessible8() throws Throwable { Method m = Field.class.getMethod("setAccessible", boolean.class); unreflectAndInvokeSetAccessible(m, inaccessibleField()); } // -- Test sub-classes of AccessibleObject -- /** * Custom AccessibleObject objects. One class overrides setAccessible, the other * does not override this method. */ @DataProvider(name = "customAccessibleObjects") static Object[][] customAccessibleObjectClasses() { return new Object[][] { { new S1() }, { new S2() } }; } public static class S1 extends AccessibleObject { } public static class S2 extends AccessibleObject { @Override public void setAccessible(boolean flag) { super.setAccessible(flag); } } void findAndInvokeSetAccessible(Lookup lookup, AccessibleObject obj) throws Throwable { MethodType mt = MethodType.methodType(void.class, boolean.class); MethodHandle mh = lookup.findVirtual(obj.getClass(), "setAccessible", mt); mh.invoke(obj, true); assertTrue(obj.isAccessible()); } void unreflectAndInvokeSetAccessible(Lookup lookup, AccessibleObject obj) throws Throwable { Method m = obj.getClass().getMethod("setAccessible", boolean.class); MethodHandle mh = lookup.unreflect(m); mh.invoke(obj, true); assertTrue(obj.isAccessible()); } /** * Using publicLookup, create a method handle to setAccessible and invoke it * on a custom AccessibleObject object. */ @Test(expectedExceptions = IllegalAccessException.class) public void testPublicLookupSubclass1() throws Throwable { // S1 does not override setAccessible findAndInvokeSetAccessible(MethodHandles.publicLookup(), new S1()); } @Test public void testPublicLookupSubclass2() throws Throwable { // S2 overrides setAccessible findAndInvokeSetAccessible(MethodHandles.publicLookup(), new S2()); } @Test(expectedExceptions = IllegalAccessException.class) public void testPublicLookupSubclass3() throws Throwable { // S1 does not override setAccessible unreflectAndInvokeSetAccessible(MethodHandles.publicLookup(), new S1()); } @Test public void testPublicLookupSubclass4() throws Throwable { // S2 overrides setAccessible unreflectAndInvokeSetAccessible(MethodHandles.publicLookup(), new S2()); } /** * Using a full power lookup, create a method handle to setAccessible and * invoke it on a custom AccessibleObject object. */ @Test(dataProvider = "customAccessibleObjects") public void testLookupSubclass1(AccessibleObject obj) throws Throwable { findAndInvokeSetAccessible(MethodHandles.lookup(), obj); } @Test(dataProvider = "customAccessibleObjects") public void testLookupSubclass2(AccessibleObject obj) throws Throwable { unreflectAndInvokeSetAccessible(MethodHandles.lookup(), obj); } /** * Using a full power lookup, create a method handle to setAccessible on a * sub-class of AccessibleObject and then attempt to invoke it on a Field object. */ @Test(dataProvider = "customAccessibleObjects", expectedExceptions = ClassCastException.class) public void testLookupSubclass3(AccessibleObject obj) throws Throwable { MethodType mt = MethodType.methodType(void.class, boolean.class); Lookup lookup = MethodHandles.lookup(); MethodHandle mh = lookup.findVirtual(obj.getClass(), "setAccessible", mt); mh.invoke(accessibleField(), true); // should throw ClassCastException } /** * Using a full power lookup, use unreflect to create a method handle to * setAccessible on a sub-class of AccessibleObject, then attempt to invoke * it on a Field object. */ @Test public void testLookupSubclass4() throws Throwable { // S1 does not override setAccessible Method m = S1.class.getMethod("setAccessible", boolean.class); assertTrue(m.getDeclaringClass() == AccessibleObject.class); MethodHandle mh = MethodHandles.lookup().unreflect(m); Field f = accessibleField(); mh.invoke(f, true); assertTrue(f.isAccessible()); } @Test(expectedExceptions = InaccessibleObjectException.class) public void testLookupSubclass5() throws Throwable { // S1 does not override setAccessible Method m = S1.class.getMethod("setAccessible", boolean.class); assertTrue(m.getDeclaringClass() == AccessibleObject.class); MethodHandle mh = MethodHandles.lookup().unreflect(m); mh.invoke(inaccessibleField(), true); // should throw InaccessibleObjectException } @Test(expectedExceptions = ClassCastException.class) public void testLookupSubclass6() throws Throwable { // S2 overrides setAccessible Method m = S2.class.getMethod("setAccessible", boolean.class); assertTrue(m.getDeclaringClass() == S2.class); MethodHandle mh = MethodHandles.lookup().unreflect(m); mh.invoke(accessibleField(), true); // should throw ClassCastException } @Test(expectedExceptions = ClassCastException.class) public void testLookupSubclass7() throws Throwable { // S2 overrides setAccessible Method m = S2.class.getMethod("setAccessible", boolean.class); assertTrue(m.getDeclaringClass() == S2.class); MethodHandle mh = MethodHandles.lookup().unreflect(m); mh.invoke(inaccessibleField(), true); // should throw ClassCastException } // -- supporting methods -- /** * Returns a stream of all caller sensitive methods on public classes in packages * exported by a named module. */ static Stream callerSensitiveMethods(Module module) { assert module.isNamed(); ModuleReference mref = module.getLayer().configuration() .findModule(module.getName()) .orElseThrow(() -> new RuntimeException()) .reference(); // find all ".class" resources in the module // transform the resource name to a class name // load every class in the exported packages // return the caller sensitive methods of the public classes try (ModuleReader reader = mref.open()) { return reader.list() .filter(rn -> rn.endsWith(".class")) .map(rn -> rn.substring(0, rn.length() - 6) .replace('/', '.')) .filter(cn -> module.isExported(packageName(cn))) .map(cn -> Class.forName(module, cn)) .filter(refc -> refc != null && Modifier.isPublic(refc.getModifiers())) .map(refc -> callerSensitiveMethods(refc)) .flatMap(List::stream); } catch (IOException ioe) { throw new UncheckedIOException(ioe); } } static String packageName(String cn) { int last = cn.lastIndexOf('.'); if (last > 0) { return cn.substring(0, last); } else { return ""; } } /** * Returns a list of the caller sensitive methods directly declared by the given * class. */ static List callerSensitiveMethods(Class refc) { return Arrays.stream(refc.getDeclaredMethods()) .filter(m -> m.isAnnotationPresent(CallerSensitive.class)) .collect(Collectors.toList()); } /** * Returns a short description of the given method for tracing purposes. */ static String shortDescription(Method m) { var sb = new StringBuilder(); sb.append(m.getDeclaringClass().getName()); sb.append('.'); sb.append(m.getName()); sb.append('('); StringJoiner sj = new StringJoiner(","); for (Class parameterType : m.getParameterTypes()) { sj.add(parameterType.getTypeName()); } sb.append(sj); sb.append(')'); return sb.toString(); } }