blob: a8aa649059e00ffa83ddc9f1c0a0ab715b6d3ad2 [file] [log] [blame]
/*
* 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<? extends AccessibleObject> 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<Method> 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<Method> 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();
}
}