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