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 8327639 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.BeforeAll; 55 import org.junit.jupiter.api.Test; 56 import org.junit.jupiter.params.ParameterizedTest; 57 import org.junit.jupiter.params.provider.MethodSource; 58 59 public class CallerSensitiveAccess { 60 // Cache the list of Caller Sensitive Methods 61 private static List<Method> CALLER_SENSITIVE_METHODS; 62 63 @BeforeAll 64 public static void setupCallerSensitiveMethods() { 65 CALLER_SENSITIVE_METHODS = callerSensitiveMethods(Object.class.getModule()); 66 } 67 68 /** 69 * Caller sensitive methods in APIs exported by java.base. 70 */ 71 static Object[][] callerSensitiveMethods() { 72 return CALLER_SENSITIVE_METHODS.stream() 73 .map(m -> new Object[]{m, shortDescription(m)}) 74 .toArray(Object[][]::new); 75 } 76 77 /** 78 * Using publicLookup, attempt to use findVirtual or findStatic to obtain a 79 * method handle to a caller sensitive method. 80 */ 81 @ParameterizedTest 82 @MethodSource("callerSensitiveMethods") 83 public void testPublicLookupFind(Method method, String desc) throws Exception { 84 Lookup lookup = MethodHandles.publicLookup(); 85 Class<?> refc = method.getDeclaringClass(); 86 String name = method.getName(); 87 MethodType mt = MethodType.methodType(method.getReturnType(), method.getParameterTypes()); 88 assertThrows(IllegalAccessException.class, () -> { 89 if (Modifier.isStatic(method.getModifiers())) { 90 lookup.findStatic(refc, name, mt); 91 } else { 92 lookup.findVirtual(refc, name, mt); 93 } 94 }); 95 } 96 97 /** 98 * Using publicLookup, attempt to use unreflect to obtain a method handle to a 99 * caller sensitive method. 100 */ 101 @ParameterizedTest 102 @MethodSource("callerSensitiveMethods") 103 public void testPublicLookupUnreflect(Method method, String desc) throws Exception { 104 assertThrows(IllegalAccessException.class, () -> MethodHandles.publicLookup().unreflect(method)); 105 } 106 107 /** 108 * public accessible caller sensitive methods in APIs exported by java.base. 109 */ 110 static Object[][] accessibleCallerSensitiveMethods() { 111 return CALLER_SENSITIVE_METHODS.stream() 112 .filter(m -> Modifier.isPublic(m.getModifiers())) 113 .map(m -> { m.setAccessible(true); return m; }) 114 .map(m -> new Object[] { m, shortDescription(m) }) 115 .toArray(Object[][]::new); 116 } 117 118 /** 119 * Using publicLookup, attempt to use unreflect to obtain a method handle to a 120 * caller sensitive method. 121 */ 122 @ParameterizedTest 123 @MethodSource("accessibleCallerSensitiveMethods") 124 public void testLookupUnreflect(Method method, String desc) throws Exception { 125 assertThrows(IllegalAccessException.class, () -> MethodHandles.publicLookup().unreflect(method)); 126 } 127 128 /** 129 * Using a Lookup with no original access that can't lookup caller-sensitive 130 * method 131 */ 132 @ParameterizedTest 133 @MethodSource("callerSensitiveMethods") 134 public void testLookupNoOriginalAccessFind(Method method, String desc) throws Exception { 135 Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.ORIGINAL); 136 assertTrue(lookup.hasFullPrivilegeAccess()); 137 Class<?> refc = method.getDeclaringClass(); 138 String name = method.getName(); 139 MethodType mt = MethodType.methodType(method.getReturnType(), method.getParameterTypes()); 140 assertThrows(IllegalAccessException.class, () -> { 141 if (Modifier.isStatic(method.getModifiers())) { 142 lookup.findStatic(refc, name, mt); 143 } else { 144 lookup.findVirtual(refc, name, mt); 145 } 146 }); 147 } 148 149 /** 150 * Using a Lookup with no original access that can't unreflect caller-sensitive 151 * method 152 */ 153 @ParameterizedTest 154 @MethodSource("callerSensitiveMethods") 155 public void testLookupNoOriginalAccessUnreflect(Method method, String desc) throws Exception { 156 Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.ORIGINAL); 157 assertTrue(lookup.hasFullPrivilegeAccess()); 158 assertThrows(IllegalAccessException.class, () -> lookup.unreflect(method)); 159 } 160 161 // -- Test method handles to setAccessible -- 162 163 private int aField; 164 165 Field accessibleField() { 166 var clazz = getClass(); 167 return assertDoesNotThrow(() -> clazz.getDeclaredField("aField")); 168 } 169 170 Field inaccessibleField() { 171 return assertDoesNotThrow(() -> String.class.getDeclaredField("hash")); 172 } 173 174 void findAndInvokeSetAccessible(Class<? extends AccessibleObject> refc, Field f) throws Throwable { 175 MethodType mt = MethodType.methodType(void.class, boolean.class); 176 MethodHandle mh = MethodHandles.lookup().findVirtual(refc, "setAccessible", mt); 177 mh.invoke(f, true); // may throw InaccessibleObjectException 178 assertTrue(f.isAccessible()); 179 } 180 181 void unreflectAndInvokeSetAccessible(Method m, Field f) throws Throwable { 182 assertTrue(m.getName().equals("setAccessible")); 183 assertFalse(Modifier.isStatic(m.getModifiers())); 184 MethodHandle mh = MethodHandles.lookup().unreflect(m); 185 mh.invoke(f, true); // may throw InaccessibleObjectException 186 assertTrue(f.isAccessible()); 187 } 188 189 /** 190 * Create a method handle to setAccessible and use it to suppress access to an 191 * accessible member. 192 */ 193 @Test 194 public void testSetAccessible1() throws Throwable { 195 findAndInvokeSetAccessible(AccessibleObject.class, accessibleField()); 196 } 197 @Test 198 public void testSetAccessible2() throws Throwable { 199 findAndInvokeSetAccessible(Field.class, accessibleField()); 200 } 201 @Test 202 public void testSetAccessible3() throws Throwable { 203 Method m = AccessibleObject.class.getMethod("setAccessible", boolean.class); 204 unreflectAndInvokeSetAccessible(m, accessibleField()); 205 } 206 @Test 207 public void testSetAccessible4() throws Throwable { 208 Method m = Field.class.getMethod("setAccessible", boolean.class); 209 unreflectAndInvokeSetAccessible(m, accessibleField()); 210 } 211 212 /** 213 * Create a method handle to setAccessible and attempt to use it to suppress 214 * access to an inaccessible member. 215 */ 216 @Test 217 public void testSetAccessible5() throws Throwable { 218 assertThrows(InaccessibleObjectException.class, () -> findAndInvokeSetAccessible(AccessibleObject.class, inaccessibleField())); 219 } 220 @Test 221 public void testSetAccessible6() throws Throwable { 222 assertThrows(InaccessibleObjectException.class, () -> findAndInvokeSetAccessible(Field.class, inaccessibleField())); 223 } 224 @Test 225 public void testSetAccessible7() throws Throwable { 226 Method m = AccessibleObject.class.getMethod("setAccessible", boolean.class); 227 assertThrows(InaccessibleObjectException.class, () -> unreflectAndInvokeSetAccessible(m, inaccessibleField())); 228 } 229 @Test 230 public void testSetAccessible8() throws Throwable { 231 Method m = Field.class.getMethod("setAccessible", boolean.class); 232 assertThrows(InaccessibleObjectException.class, () -> unreflectAndInvokeSetAccessible(m, inaccessibleField())); 233 } 234 235 236 // -- Test sub-classes of AccessibleObject -- 237 238 /** 239 * Custom AccessibleObject objects. One class overrides setAccessible, the other 240 * does not override this method. 241 */ 242 static Object[][] customAccessibleObjectClasses() { 243 return new Object[][] { { new S1() }, { new S2() } }; 244 } 245 public static class S1 extends AccessibleObject { } 246 public static class S2 extends AccessibleObject { 247 @Override 248 public void setAccessible(boolean flag) { 249 super.setAccessible(flag); 250 } 251 } 252 253 void findAndInvokeSetAccessible(Lookup lookup, AccessibleObject obj) throws Throwable { 254 MethodType mt = MethodType.methodType(void.class, boolean.class); 255 MethodHandle mh = lookup.findVirtual(obj.getClass(), "setAccessible", mt); 256 mh.invoke(obj, true); 257 assertTrue(obj.isAccessible()); 258 } 259 260 void unreflectAndInvokeSetAccessible(Lookup lookup, AccessibleObject obj) throws Throwable { 261 Method m = obj.getClass().getMethod("setAccessible", boolean.class); 262 MethodHandle mh = lookup.unreflect(m); 263 mh.invoke(obj, true); 264 assertTrue(obj.isAccessible()); 265 } 266 267 /** 268 * Using publicLookup, create a method handle to setAccessible and invoke it 269 * on a custom AccessibleObject object. 270 */ 271 @Test 272 public void testPublicLookupSubclass1() throws Throwable { 273 // S1 does not override setAccessible 274 assertThrows(IllegalAccessException.class, () -> findAndInvokeSetAccessible(MethodHandles.publicLookup(), new S1())); 275 } 276 @Test 277 public void testPublicLookupSubclass2() throws Throwable { 278 // S2 overrides setAccessible 279 findAndInvokeSetAccessible(MethodHandles.publicLookup(), new S2()); 280 } 281 @Test 282 public void testPublicLookupSubclass3() throws Throwable { 283 // S1 does not override setAccessible 284 assertThrows(IllegalAccessException.class, () -> unreflectAndInvokeSetAccessible(MethodHandles.publicLookup(), new S1())); 285 } 286 @Test 287 public void testPublicLookupSubclass4() throws Throwable { 288 // S2 overrides setAccessible 289 unreflectAndInvokeSetAccessible(MethodHandles.publicLookup(), new S2()); 290 } 291 292 /** 293 * Using a full power lookup, create a method handle to setAccessible and 294 * invoke it on a custom AccessibleObject object. 295 */ 296 @ParameterizedTest 297 @MethodSource("customAccessibleObjectClasses") 298 public void testLookupSubclass1(AccessibleObject obj) throws Throwable { 299 findAndInvokeSetAccessible(MethodHandles.lookup(), obj); 300 } 301 @ParameterizedTest 302 @MethodSource("customAccessibleObjectClasses") 303 public void testLookupSubclass2(AccessibleObject obj) throws Throwable { 304 unreflectAndInvokeSetAccessible(MethodHandles.lookup(), obj); 305 } 306 307 /** 308 * Using a full power lookup, create a method handle to setAccessible on a 309 * sub-class of AccessibleObject and then attempt to invoke it on a Field object. 310 */ 311 @ParameterizedTest 312 @MethodSource("customAccessibleObjectClasses") 313 public void testLookupSubclass3(AccessibleObject obj) throws Throwable { 314 MethodType mt = MethodType.methodType(void.class, boolean.class); 315 Lookup lookup = MethodHandles.lookup(); 316 MethodHandle mh = lookup.findVirtual(obj.getClass(), "setAccessible", mt); 317 assertThrows(ClassCastException.class, () -> mh.invoke(accessibleField(), true)); 318 } 319 320 /** 321 * Using a full power lookup, use unreflect to create a method handle to 322 * setAccessible on a sub-class of AccessibleObject, then attempt to invoke 323 * it on a Field object. 324 */ 325 @Test 326 public void testLookupSubclass4() throws Throwable { 327 // S1 does not override setAccessible 328 Method m = S1.class.getMethod("setAccessible", boolean.class); 329 assertTrue(m.getDeclaringClass() == AccessibleObject.class); 330 MethodHandle mh = MethodHandles.lookup().unreflect(m); 331 Field f = accessibleField(); 332 mh.invoke(f, true); 333 assertTrue(f.isAccessible()); 334 } 335 @Test 336 public void testLookupSubclass5() throws Throwable { 337 // S1 does not override setAccessible 338 Method m = S1.class.getMethod("setAccessible", boolean.class); 339 assertTrue(m.getDeclaringClass() == AccessibleObject.class); 340 MethodHandle mh = MethodHandles.lookup().unreflect(m); 341 assertThrows(InaccessibleObjectException.class, () -> mh.invoke(inaccessibleField(), true)); 342 } 343 @Test 344 public void testLookupSubclass6() throws Throwable { 345 // S2 overrides setAccessible 346 Method m = S2.class.getMethod("setAccessible", boolean.class); 347 assertTrue(m.getDeclaringClass() == S2.class); 348 MethodHandle mh = MethodHandles.lookup().unreflect(m); 349 assertThrows(ClassCastException.class, () -> mh.invoke(accessibleField(), true)); 350 } 351 @Test 352 public void testLookupSubclass7() throws Throwable { 353 // S2 overrides setAccessible 354 Method m = S2.class.getMethod("setAccessible", boolean.class); 355 assertTrue(m.getDeclaringClass() == S2.class); 356 MethodHandle mh = MethodHandles.lookup().unreflect(m); 357 assertThrows(ClassCastException.class, () -> mh.invoke(inaccessibleField(), true)); 358 } 359 360 /** 361 * Field::getInt and Field::setInt are caller-sensitive methods. 362 * Test access to private field. 363 */ 364 private static int privateField = 0; 365 @Test 366 public void testPrivateField() throws Throwable { 367 Field f = CallerSensitiveAccess.class.getDeclaredField("privateField"); 368 // Field::setInt 369 MethodType mtype = MethodType.methodType(void.class, Object.class, int.class); 370 MethodHandle mh = MethodHandles.lookup().findVirtual(Field.class, "setInt", mtype); 371 mh.invokeExact(f, (Object)null, 5); 372 // Field::getInt 373 mh = MethodHandles.lookup().findVirtual(Field.class, "getInt", MethodType.methodType(int.class, Object.class)); 374 int value = (int)mh.invokeExact(f, (Object)null); 375 assertEquals(5, value); 376 } 377 378 private static class Inner { 379 private static boolean privateField = false; 380 } 381 382 @Test 383 public void testInnerPrivateField() throws Throwable { 384 Field f = Inner.class.getDeclaredField("privateField"); 385 // Field::setInt 386 MethodType mtype = MethodType.methodType(void.class, Object.class, boolean.class); 387 MethodHandle mh = MethodHandles.lookup().findVirtual(Field.class, "setBoolean", mtype); 388 mh.invokeExact(f, (Object)null, true); 389 // Field::getInt 390 mh = MethodHandles.lookup().findVirtual(Field.class, "getBoolean", MethodType.methodType(boolean.class, Object.class)); 391 boolean value = (boolean)mh.invokeExact(f, (Object)null); 392 assertTrue(value); 393 } 394 395 // -- supporting methods -- 396 397 /** 398 * Returns a List of all caller sensitive methods on public classes in packages 399 * exported by a named module. 400 * Returns a List instead of a stream so the ModuleReader can be closed before returning. 401 */ 402 static List<Method> callerSensitiveMethods(Module module) { 403 assertTrue(module.isNamed()); 404 ModuleReference mref = module.getLayer().configuration() 405 .findModule(module.getName()) 406 .orElseThrow(() -> new RuntimeException()) 407 .reference(); 408 // find all ".class" resources in the module 409 // transform the resource name to a class name 410 // load every class in the exported packages 411 // return the caller sensitive methods of the public classes 412 try (ModuleReader reader = mref.open()) { 413 return reader.list() 414 .filter(rn -> rn.endsWith(".class")) 415 .map(rn -> rn.substring(0, rn.length() - 6) 416 .replace('/', '.')) 417 .filter(cn -> module.isExported(packageName(cn))) 418 .map(cn -> Class.forName(module, cn)) 419 .filter(refc -> refc != null 420 && Modifier.isPublic(refc.getModifiers())) 421 .map(refc -> callerSensitiveMethods(refc)) 422 .flatMap(List::stream).toList(); 423 } catch (IOException ioe) { 424 throw new UncheckedIOException(ioe); 425 } 426 } 427 428 static String packageName(String cn) { 429 int last = cn.lastIndexOf('.'); 430 if (last > 0) { 431 return cn.substring(0, last); 432 } else { 433 return ""; 434 } 435 } 436 437 /** 438 * Returns a list of the caller sensitive methods directly declared by the given 439 * class. 440 */ 441 static List<Method> callerSensitiveMethods(Class<?> refc) { 442 return Arrays.stream(refc.getDeclaredMethods()) 443 .filter(m -> m.isAnnotationPresent(CallerSensitive.class)) 444 .collect(Collectors.toList()); 445 } 446 447 /** 448 * Returns a short description of the given method for tracing purposes. 449 */ 450 static String shortDescription(Method m) { 451 var sb = new StringBuilder(); 452 sb.append(m.getDeclaringClass().getName()); 453 sb.append('.'); 454 sb.append(m.getName()); 455 sb.append('('); 456 StringJoiner sj = new StringJoiner(","); 457 for (Class<?> parameterType : m.getParameterTypes()) { 458 sj.add(parameterType.getTypeName()); 459 } 460 sb.append(sj); 461 sb.append(')'); 462 return sb.toString(); 463 } 464 } --- EOF ---