1 /* 2 * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* @test 25 * @bug 8196830 8235351 8257874 26 * @modules java.base/jdk.internal.reflect 27 * @run junit/othervm CallerSensitiveAccess 28 * @summary Check Lookup findVirtual, findStatic and unreflect behavior with 29 * caller sensitive methods with focus on AccessibleObject.setAccessible 30 */ 31 32 import java.io.IOException; 33 import java.io.UncheckedIOException; 34 import java.lang.invoke.MethodHandle; 35 import java.lang.invoke.MethodHandles; 36 import java.lang.invoke.MethodHandles.Lookup; 37 import java.lang.invoke.MethodType; 38 import java.lang.module.ModuleReader; 39 import java.lang.module.ModuleReference; 40 import java.lang.reflect.AccessibleObject; 41 import java.lang.reflect.Field; 42 import java.lang.reflect.InaccessibleObjectException; 43 import java.lang.reflect.Method; 44 import java.lang.reflect.Modifier; 45 import java.util.Arrays; 46 import java.util.List; 47 import java.util.StringJoiner; 48 import java.util.stream.Collectors; 49 import java.util.stream.Stream; 50 51 import jdk.internal.reflect.CallerSensitive; 52 53 import static org.junit.jupiter.api.Assertions.*; 54 import org.junit.jupiter.api.Test; 55 import org.junit.jupiter.params.ParameterizedTest; 56 import org.junit.jupiter.params.provider.MethodSource; 57 58 public class CallerSensitiveAccess { 59 60 /** 61 * Caller sensitive methods in APIs exported by java.base. 62 */ 63 static Object[][] callerSensitiveMethods() { 64 try (Stream<Method> stream = callerSensitiveMethods(Object.class.getModule())) { 65 return stream.map(m -> new Object[]{m, shortDescription(m)}) 66 .toArray(Object[][]::new); 67 } 68 } 69 70 /** 71 * Using publicLookup, attempt to use findVirtual or findStatic to obtain a 72 * method handle to a caller sensitive method. 73 */ 74 @ParameterizedTest 75 @MethodSource("callerSensitiveMethods") 76 public void testPublicLookupFind(Method method, String desc) throws Exception { 77 Lookup lookup = MethodHandles.publicLookup(); 78 Class<?> refc = method.getDeclaringClass(); 79 String name = method.getName(); 80 MethodType mt = MethodType.methodType(method.getReturnType(), method.getParameterTypes()); 81 assertThrows(IllegalAccessException.class, () -> { 82 if (Modifier.isStatic(method.getModifiers())) { 83 lookup.findStatic(refc, name, mt); 84 } else { 85 lookup.findVirtual(refc, name, mt); 86 } 87 }); 88 } 89 90 /** 91 * Using publicLookup, attempt to use unreflect to obtain a method handle to a 92 * caller sensitive method. 93 */ 94 @ParameterizedTest 95 @MethodSource("callerSensitiveMethods") 96 public void testPublicLookupUnreflect(Method method, String desc) throws Exception { 97 assertThrows(IllegalAccessException.class, () -> MethodHandles.publicLookup().unreflect(method)); 98 } 99 100 /** 101 * public accessible caller sensitive methods in APIs exported by java.base. 102 */ 103 static Object[][] accessibleCallerSensitiveMethods() { 104 try (Stream<Method> stream = callerSensitiveMethods(Object.class.getModule())) { 105 return stream 106 .filter(m -> Modifier.isPublic(m.getModifiers())) 107 .map(m -> { m.setAccessible(true); return m; }) 108 .map(m -> new Object[] { m, shortDescription(m) }) 109 .toArray(Object[][]::new); 110 } 111 } 112 113 /** 114 * Using publicLookup, attempt to use unreflect to obtain a method handle to a 115 * caller sensitive method. 116 */ 117 @ParameterizedTest 118 @MethodSource("accessibleCallerSensitiveMethods") 119 public void testLookupUnreflect(Method method, String desc) throws Exception { 120 assertThrows(IllegalAccessException.class, () -> MethodHandles.publicLookup().unreflect(method)); 121 } 122 123 /** 124 * Using a Lookup with no original access that can't lookup caller-sensitive 125 * method 126 */ 127 @ParameterizedTest 128 @MethodSource("callerSensitiveMethods") 129 public void testLookupNoOriginalAccessFind(Method method, String desc) throws Exception { 130 Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.ORIGINAL); 131 assertTrue(lookup.hasFullPrivilegeAccess()); 132 Class<?> refc = method.getDeclaringClass(); 133 String name = method.getName(); 134 MethodType mt = MethodType.methodType(method.getReturnType(), method.getParameterTypes()); 135 assertThrows(IllegalAccessException.class, () -> { 136 if (Modifier.isStatic(method.getModifiers())) { 137 lookup.findStatic(refc, name, mt); 138 } else { 139 lookup.findVirtual(refc, name, mt); 140 } 141 }); 142 } 143 144 /** 145 * Using a Lookup with no original access that can't unreflect caller-sensitive 146 * method 147 */ 148 @ParameterizedTest 149 @MethodSource("callerSensitiveMethods") 150 public void testLookupNoOriginalAccessUnreflect(Method method, String desc) throws Exception { 151 Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.ORIGINAL); 152 assertTrue(lookup.hasFullPrivilegeAccess()); 153 assertThrows(IllegalAccessException.class, () -> lookup.unreflect(method)); 154 } 155 156 // -- Test method handles to setAccessible -- 157 158 private int aField; 159 160 Field accessibleField() { 161 var clazz = getClass(); 162 return assertDoesNotThrow(() -> clazz.getDeclaredField("aField")); 163 } 164 165 Field inaccessibleField() { 166 return assertDoesNotThrow(() -> String.class.getDeclaredField("hash")); 167 } 168 169 void findAndInvokeSetAccessible(Class<? extends AccessibleObject> refc, Field f) throws Throwable { 170 MethodType mt = MethodType.methodType(void.class, boolean.class); 171 MethodHandle mh = MethodHandles.lookup().findVirtual(refc, "setAccessible", mt); 172 mh.invoke(f, true); // may throw InaccessibleObjectException 173 assertTrue(f.isAccessible()); 174 } 175 176 void unreflectAndInvokeSetAccessible(Method m, Field f) throws Throwable { 177 assertTrue(m.getName().equals("setAccessible")); 178 assertFalse(Modifier.isStatic(m.getModifiers())); 179 MethodHandle mh = MethodHandles.lookup().unreflect(m); 180 mh.invoke(f, true); // may throw InaccessibleObjectException 181 assertTrue(f.isAccessible()); 182 } 183 184 /** 185 * Create a method handle to setAccessible and use it to suppress access to an 186 * accessible member. 187 */ 188 @Test 189 public void testSetAccessible1() throws Throwable { 190 findAndInvokeSetAccessible(AccessibleObject.class, accessibleField()); 191 } 192 @Test 193 public void testSetAccessible2() throws Throwable { 194 findAndInvokeSetAccessible(Field.class, accessibleField()); 195 } 196 @Test 197 public void testSetAccessible3() throws Throwable { 198 Method m = AccessibleObject.class.getMethod("setAccessible", boolean.class); 199 unreflectAndInvokeSetAccessible(m, accessibleField()); 200 } 201 @Test 202 public void testSetAccessible4() throws Throwable { 203 Method m = Field.class.getMethod("setAccessible", boolean.class); 204 unreflectAndInvokeSetAccessible(m, accessibleField()); 205 } 206 207 /** 208 * Create a method handle to setAccessible and attempt to use it to suppress 209 * access to an inaccessible member. 210 */ 211 @Test 212 public void testSetAccessible5() throws Throwable { 213 assertThrows(InaccessibleObjectException.class, () -> findAndInvokeSetAccessible(AccessibleObject.class, inaccessibleField())); 214 } 215 @Test 216 public void testSetAccessible6() throws Throwable { 217 assertThrows(InaccessibleObjectException.class, () -> findAndInvokeSetAccessible(Field.class, inaccessibleField())); 218 } 219 @Test 220 public void testSetAccessible7() throws Throwable { 221 Method m = AccessibleObject.class.getMethod("setAccessible", boolean.class); 222 assertThrows(InaccessibleObjectException.class, () -> unreflectAndInvokeSetAccessible(m, inaccessibleField())); 223 } 224 @Test 225 public void testSetAccessible8() throws Throwable { 226 Method m = Field.class.getMethod("setAccessible", boolean.class); 227 assertThrows(InaccessibleObjectException.class, () -> unreflectAndInvokeSetAccessible(m, inaccessibleField())); 228 } 229 230 231 // -- Test sub-classes of AccessibleObject -- 232 233 /** 234 * Custom AccessibleObject objects. One class overrides setAccessible, the other 235 * does not override this method. 236 */ 237 static Object[][] customAccessibleObjectClasses() { 238 return new Object[][] { { new S1() }, { new S2() } }; 239 } 240 public static class S1 extends AccessibleObject { } 241 public static class S2 extends AccessibleObject { 242 @Override 243 public void setAccessible(boolean flag) { 244 super.setAccessible(flag); 245 } 246 } 247 248 void findAndInvokeSetAccessible(Lookup lookup, AccessibleObject obj) throws Throwable { 249 MethodType mt = MethodType.methodType(void.class, boolean.class); 250 MethodHandle mh = lookup.findVirtual(obj.getClass(), "setAccessible", mt); 251 mh.invoke(obj, true); 252 assertTrue(obj.isAccessible()); 253 } 254 255 void unreflectAndInvokeSetAccessible(Lookup lookup, AccessibleObject obj) throws Throwable { 256 Method m = obj.getClass().getMethod("setAccessible", boolean.class); 257 MethodHandle mh = lookup.unreflect(m); 258 mh.invoke(obj, true); 259 assertTrue(obj.isAccessible()); 260 } 261 262 /** 263 * Using publicLookup, create a method handle to setAccessible and invoke it 264 * on a custom AccessibleObject object. 265 */ 266 @Test 267 public void testPublicLookupSubclass1() throws Throwable { 268 // S1 does not override setAccessible 269 assertThrows(IllegalAccessException.class, () -> findAndInvokeSetAccessible(MethodHandles.publicLookup(), new S1())); 270 } 271 @Test 272 public void testPublicLookupSubclass2() throws Throwable { 273 // S2 overrides setAccessible 274 findAndInvokeSetAccessible(MethodHandles.publicLookup(), new S2()); 275 } 276 @Test 277 public void testPublicLookupSubclass3() throws Throwable { 278 // S1 does not override setAccessible 279 assertThrows(IllegalAccessException.class, () -> unreflectAndInvokeSetAccessible(MethodHandles.publicLookup(), new S1())); 280 } 281 @Test 282 public void testPublicLookupSubclass4() throws Throwable { 283 // S2 overrides setAccessible 284 unreflectAndInvokeSetAccessible(MethodHandles.publicLookup(), new S2()); 285 } 286 287 /** 288 * Using a full power lookup, create a method handle to setAccessible and 289 * invoke it on a custom AccessibleObject object. 290 */ 291 @ParameterizedTest 292 @MethodSource("customAccessibleObjectClasses") 293 public void testLookupSubclass1(AccessibleObject obj) throws Throwable { 294 findAndInvokeSetAccessible(MethodHandles.lookup(), obj); 295 } 296 @ParameterizedTest 297 @MethodSource("customAccessibleObjectClasses") 298 public void testLookupSubclass2(AccessibleObject obj) throws Throwable { 299 unreflectAndInvokeSetAccessible(MethodHandles.lookup(), obj); 300 } 301 302 /** 303 * Using a full power lookup, create a method handle to setAccessible on a 304 * sub-class of AccessibleObject and then attempt to invoke it on a Field object. 305 */ 306 @ParameterizedTest 307 @MethodSource("customAccessibleObjectClasses") 308 public void testLookupSubclass3(AccessibleObject obj) throws Throwable { 309 MethodType mt = MethodType.methodType(void.class, boolean.class); 310 Lookup lookup = MethodHandles.lookup(); 311 MethodHandle mh = lookup.findVirtual(obj.getClass(), "setAccessible", mt); 312 assertThrows(ClassCastException.class, () -> mh.invoke(accessibleField(), true)); 313 } 314 315 /** 316 * Using a full power lookup, use unreflect to create a method handle to 317 * setAccessible on a sub-class of AccessibleObject, then attempt to invoke 318 * it on a Field object. 319 */ 320 @Test 321 public void testLookupSubclass4() throws Throwable { 322 // S1 does not override setAccessible 323 Method m = S1.class.getMethod("setAccessible", boolean.class); 324 assertTrue(m.getDeclaringClass() == AccessibleObject.class); 325 MethodHandle mh = MethodHandles.lookup().unreflect(m); 326 Field f = accessibleField(); 327 mh.invoke(f, true); 328 assertTrue(f.isAccessible()); 329 } 330 @Test 331 public void testLookupSubclass5() throws Throwable { 332 // S1 does not override setAccessible 333 Method m = S1.class.getMethod("setAccessible", boolean.class); 334 assertTrue(m.getDeclaringClass() == AccessibleObject.class); 335 MethodHandle mh = MethodHandles.lookup().unreflect(m); 336 assertThrows(InaccessibleObjectException.class, () -> mh.invoke(inaccessibleField(), true)); 337 } 338 @Test 339 public void testLookupSubclass6() throws Throwable { 340 // S2 overrides setAccessible 341 Method m = S2.class.getMethod("setAccessible", boolean.class); 342 assertTrue(m.getDeclaringClass() == S2.class); 343 MethodHandle mh = MethodHandles.lookup().unreflect(m); 344 assertThrows(ClassCastException.class, () -> mh.invoke(accessibleField(), true)); 345 } 346 @Test 347 public void testLookupSubclass7() throws Throwable { 348 // S2 overrides setAccessible 349 Method m = S2.class.getMethod("setAccessible", boolean.class); 350 assertTrue(m.getDeclaringClass() == S2.class); 351 MethodHandle mh = MethodHandles.lookup().unreflect(m); 352 assertThrows(ClassCastException.class, () -> mh.invoke(inaccessibleField(), true)); 353 } 354 355 /** 356 * Field::getInt and Field::setInt are caller-sensitive methods. 357 * Test access to private field. 358 */ 359 private static int privateField = 0; 360 @Test 361 public void testPrivateField() throws Throwable { 362 Field f = CallerSensitiveAccess.class.getDeclaredField("privateField"); 363 // Field::setInt 364 MethodType mtype = MethodType.methodType(void.class, Object.class, int.class); 365 MethodHandle mh = MethodHandles.lookup().findVirtual(Field.class, "setInt", mtype); 366 mh.invokeExact(f, (Object)null, 5); 367 // Field::getInt 368 mh = MethodHandles.lookup().findVirtual(Field.class, "getInt", MethodType.methodType(int.class, Object.class)); 369 int value = (int)mh.invokeExact(f, (Object)null); 370 assertEquals(5, value); 371 } 372 373 private static class Inner { 374 private static boolean privateField = false; 375 } 376 377 @Test 378 public void testInnerPrivateField() throws Throwable { 379 Field f = Inner.class.getDeclaredField("privateField"); 380 // Field::setInt 381 MethodType mtype = MethodType.methodType(void.class, Object.class, boolean.class); 382 MethodHandle mh = MethodHandles.lookup().findVirtual(Field.class, "setBoolean", mtype); 383 mh.invokeExact(f, (Object)null, true); 384 // Field::getInt 385 mh = MethodHandles.lookup().findVirtual(Field.class, "getBoolean", MethodType.methodType(boolean.class, Object.class)); 386 boolean value = (boolean)mh.invokeExact(f, (Object)null); 387 assertTrue(value); 388 } 389 390 // -- supporting methods -- 391 392 /** 393 * Returns a stream of all caller sensitive methods on public classes in packages 394 * exported by a named module. 395 */ 396 static Stream<Method> callerSensitiveMethods(Module module) { 397 assertTrue(module.isNamed()); 398 ModuleReference mref = module.getLayer().configuration() 399 .findModule(module.getName()) 400 .orElseThrow(() -> new RuntimeException()) 401 .reference(); 402 // find all ".class" resources in the module 403 // transform the resource name to a class name 404 // load every class in the exported packages 405 // return the caller sensitive methods of the public classes 406 try (ModuleReader reader = mref.open()) { 407 return reader.list() 408 .filter(rn -> rn.endsWith(".class")) 409 .map(rn -> rn.substring(0, rn.length() - 6) 410 .replace('/', '.')) 411 .filter(cn -> module.isExported(packageName(cn))) 412 .map(cn -> Class.forName(module, cn)) 413 .filter(refc -> refc != null 414 && Modifier.isPublic(refc.getModifiers())) 415 .map(refc -> callerSensitiveMethods(refc)) 416 .flatMap(List::stream); 417 } catch (IOException ioe) { 418 throw new UncheckedIOException(ioe); 419 } 420 } 421 422 static String packageName(String cn) { 423 int last = cn.lastIndexOf('.'); 424 if (last > 0) { 425 return cn.substring(0, last); 426 } else { 427 return ""; 428 } 429 } 430 431 /** 432 * Returns a list of the caller sensitive methods directly declared by the given 433 * class. 434 */ 435 static List<Method> callerSensitiveMethods(Class<?> refc) { 436 return Arrays.stream(refc.getDeclaredMethods()) 437 .filter(m -> m.isAnnotationPresent(CallerSensitive.class)) 438 .collect(Collectors.toList()); 439 } 440 441 /** 442 * Returns a short description of the given method for tracing purposes. 443 */ 444 static String shortDescription(Method m) { 445 var sb = new StringBuilder(); 446 sb.append(m.getDeclaringClass().getName()); 447 sb.append('.'); 448 sb.append(m.getName()); 449 sb.append('('); 450 StringJoiner sj = new StringJoiner(","); 451 for (Class<?> parameterType : m.getParameterTypes()) { 452 sj.add(parameterType.getTypeName()); 453 } 454 sb.append(sj); 455 sb.append(')'); 456 return sb.toString(); 457 } 458 } --- EOF ---