1 /* 2 * Copyright (c) 2018, 2021, 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 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.DataProvider; 54 import org.testng.annotations.NoInjection; 55 import org.testng.annotations.Test; 56 import static org.testng.Assert.*; 57 58 public class CallerSensitiveAccess { 59 60 /** 61 * Caller sensitive methods in APIs exported by java.base. 62 */ 63 @DataProvider(name = "callerSensitiveMethods") 64 static Object[][] callerSensitiveMethods() { 65 try (Stream<Method> stream = callerSensitiveMethods(Object.class.getModule())) { 66 return stream.map(m -> new Object[]{m, shortDescription(m)}) 67 .toArray(Object[][]::new); 68 } 69 } 70 71 /** 72 * Using publicLookup, attempt to use findVirtual or findStatic to obtain a 73 * method handle to a caller sensitive method. 74 */ 75 @Test(dataProvider = "callerSensitiveMethods", 76 expectedExceptions = IllegalAccessException.class) 77 public void testPublicLookupFind(@NoInjection Method method, String desc) throws Exception { 78 Lookup lookup = MethodHandles.publicLookup(); 79 Class<?> refc = method.getDeclaringClass(); 80 String name = method.getName(); 81 MethodType mt = MethodType.methodType(method.getReturnType(), method.getParameterTypes()); 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 * Using publicLookup, attempt to use unreflect to obtain a method handle to a 91 * caller sensitive method. 92 */ 93 @Test(dataProvider = "callerSensitiveMethods", 94 expectedExceptions = IllegalAccessException.class) 95 public void testPublicLookupUnreflect(@NoInjection Method method, String desc) throws Exception { 96 MethodHandles.publicLookup().unreflect(method); 97 } 98 99 /** 100 * public accessible caller sensitive methods in APIs exported by java.base. 101 */ 102 @DataProvider(name = "accessibleCallerSensitiveMethods") 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 @Test(dataProvider = "accessibleCallerSensitiveMethods", 118 expectedExceptions = IllegalAccessException.class) 119 public void testLookupUnreflect(@NoInjection Method method, String desc) throws Exception { 120 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 @Test(dataProvider = "callerSensitiveMethods", 128 expectedExceptions = IllegalAccessException.class) 129 public void testLookupNoOriginalAccessFind(@NoInjection 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 if (Modifier.isStatic(method.getModifiers())) { 136 lookup.findStatic(refc, name, mt); 137 } else { 138 lookup.findVirtual(refc, name, mt); 139 } 140 } 141 142 /** 143 * Using a Lookup with no original access that can't unreflect caller-sensitive 144 * method 145 */ 146 @Test(dataProvider = "callerSensitiveMethods", 147 expectedExceptions = IllegalAccessException.class) 148 public void testLookupNoOriginalAccessUnreflect(@NoInjection Method method, String desc) throws Exception { 149 Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.ORIGINAL); 150 assertTrue(lookup.hasFullPrivilegeAccess()); 151 lookup.unreflect(method); 152 } 153 154 // -- Test method handles to setAccessible -- 155 156 private int aField; 157 158 Field accessibleField() { 159 try { 160 return getClass().getDeclaredField("aField"); 161 } catch (NoSuchFieldException e) { 162 fail(); 163 return null; 164 } 165 } 166 167 Field inaccessibleField() { 168 try { 169 return String.class.getDeclaredField("hash"); 170 } catch (NoSuchFieldException e) { 171 fail(); 172 return null; 173 } 174 } 175 176 void findAndInvokeSetAccessible(Class<? extends AccessibleObject> refc, Field f) throws Throwable { 177 MethodType mt = MethodType.methodType(void.class, boolean.class); 178 MethodHandle mh = MethodHandles.lookup().findVirtual(refc, "setAccessible", mt); 179 mh.invoke(f, true); // may throw InaccessibleObjectException 180 assertTrue(f.isAccessible()); 181 } 182 183 void unreflectAndInvokeSetAccessible(Method m, Field f) throws Throwable { 184 assertTrue(m.getName().equals("setAccessible")); 185 assertFalse(Modifier.isStatic(m.getModifiers())); 186 MethodHandle mh = MethodHandles.lookup().unreflect(m); 187 mh.invoke(f, true); // may throw InaccessibleObjectException 188 assertTrue(f.isAccessible()); 189 } 190 191 /** 192 * Create a method handle to setAccessible and use it to suppress access to an 193 * accessible member. 194 */ 195 @Test 196 public void testSetAccessible1() throws Throwable { 197 findAndInvokeSetAccessible(AccessibleObject.class, accessibleField()); 198 } 199 @Test 200 public void testSetAccessible2() throws Throwable { 201 findAndInvokeSetAccessible(Field.class, accessibleField()); 202 } 203 @Test 204 public void testSetAccessible3() throws Throwable { 205 Method m = AccessibleObject.class.getMethod("setAccessible", boolean.class); 206 unreflectAndInvokeSetAccessible(m, accessibleField()); 207 } 208 @Test 209 public void testSetAccessible4() throws Throwable { 210 Method m = Field.class.getMethod("setAccessible", boolean.class); 211 unreflectAndInvokeSetAccessible(m, accessibleField()); 212 } 213 214 /** 215 * Create a method handle to setAccessible and attempt to use it to suppress 216 * access to an inaccessible member. 217 */ 218 @Test(expectedExceptions = InaccessibleObjectException.class) 219 public void testSetAccessible5() throws Throwable { 220 findAndInvokeSetAccessible(AccessibleObject.class, inaccessibleField()); 221 } 222 @Test(expectedExceptions = InaccessibleObjectException.class) 223 public void testSetAccessible6() throws Throwable { 224 findAndInvokeSetAccessible(Field.class, inaccessibleField()); 225 } 226 @Test(expectedExceptions = InaccessibleObjectException.class) 227 public void testSetAccessible7() throws Throwable { 228 Method m = AccessibleObject.class.getMethod("setAccessible", boolean.class); 229 unreflectAndInvokeSetAccessible(m, inaccessibleField()); 230 } 231 @Test(expectedExceptions = InaccessibleObjectException.class) 232 public void testSetAccessible8() throws Throwable { 233 Method m = Field.class.getMethod("setAccessible", boolean.class); 234 unreflectAndInvokeSetAccessible(m, inaccessibleField()); 235 } 236 237 238 // -- Test sub-classes of AccessibleObject -- 239 240 /** 241 * Custom AccessibleObject objects. One class overrides setAccessible, the other 242 * does not override this method. 243 */ 244 @DataProvider(name = "customAccessibleObjects") 245 static Object[][] customAccessibleObjectClasses() { 246 return new Object[][] { { new S1() }, { new S2() } }; 247 } 248 public static class S1 extends AccessibleObject { } 249 public static class S2 extends AccessibleObject { 250 @Override 251 public void setAccessible(boolean flag) { 252 super.setAccessible(flag); 253 } 254 } 255 256 void findAndInvokeSetAccessible(Lookup lookup, AccessibleObject obj) throws Throwable { 257 MethodType mt = MethodType.methodType(void.class, boolean.class); 258 MethodHandle mh = lookup.findVirtual(obj.getClass(), "setAccessible", mt); 259 mh.invoke(obj, true); 260 assertTrue(obj.isAccessible()); 261 } 262 263 void unreflectAndInvokeSetAccessible(Lookup lookup, AccessibleObject obj) throws Throwable { 264 Method m = obj.getClass().getMethod("setAccessible", boolean.class); 265 MethodHandle mh = lookup.unreflect(m); 266 mh.invoke(obj, true); 267 assertTrue(obj.isAccessible()); 268 } 269 270 /** 271 * Using publicLookup, create a method handle to setAccessible and invoke it 272 * on a custom AccessibleObject object. 273 */ 274 @Test(expectedExceptions = IllegalAccessException.class) 275 public void testPublicLookupSubclass1() throws Throwable { 276 // S1 does not override setAccessible 277 findAndInvokeSetAccessible(MethodHandles.publicLookup(), new S1()); 278 } 279 @Test 280 public void testPublicLookupSubclass2() throws Throwable { 281 // S2 overrides setAccessible 282 findAndInvokeSetAccessible(MethodHandles.publicLookup(), new S2()); 283 } 284 @Test(expectedExceptions = IllegalAccessException.class) 285 public void testPublicLookupSubclass3() throws Throwable { 286 // S1 does not override setAccessible 287 unreflectAndInvokeSetAccessible(MethodHandles.publicLookup(), new S1()); 288 } 289 @Test 290 public void testPublicLookupSubclass4() throws Throwable { 291 // S2 overrides setAccessible 292 unreflectAndInvokeSetAccessible(MethodHandles.publicLookup(), new S2()); 293 } 294 295 /** 296 * Using a full power lookup, create a method handle to setAccessible and 297 * invoke it on a custom AccessibleObject object. 298 */ 299 @Test(dataProvider = "customAccessibleObjects") 300 public void testLookupSubclass1(AccessibleObject obj) throws Throwable { 301 findAndInvokeSetAccessible(MethodHandles.lookup(), obj); 302 } 303 @Test(dataProvider = "customAccessibleObjects") 304 public void testLookupSubclass2(AccessibleObject obj) throws Throwable { 305 unreflectAndInvokeSetAccessible(MethodHandles.lookup(), obj); 306 } 307 308 /** 309 * Using a full power lookup, create a method handle to setAccessible on a 310 * sub-class of AccessibleObject and then attempt to invoke it on a Field object. 311 */ 312 @Test(dataProvider = "customAccessibleObjects", 313 expectedExceptions = ClassCastException.class) 314 public void testLookupSubclass3(AccessibleObject obj) throws Throwable { 315 MethodType mt = MethodType.methodType(void.class, boolean.class); 316 Lookup lookup = MethodHandles.lookup(); 317 MethodHandle mh = lookup.findVirtual(obj.getClass(), "setAccessible", mt); 318 mh.invoke(accessibleField(), true); // should throw ClassCastException 319 } 320 321 /** 322 * Using a full power lookup, use unreflect to create a method handle to 323 * setAccessible on a sub-class of AccessibleObject, then attempt to invoke 324 * it on a Field object. 325 */ 326 @Test 327 public void testLookupSubclass4() throws Throwable { 328 // S1 does not override setAccessible 329 Method m = S1.class.getMethod("setAccessible", boolean.class); 330 assertTrue(m.getDeclaringClass() == AccessibleObject.class); 331 MethodHandle mh = MethodHandles.lookup().unreflect(m); 332 Field f = accessibleField(); 333 mh.invoke(f, true); 334 assertTrue(f.isAccessible()); 335 } 336 @Test(expectedExceptions = InaccessibleObjectException.class) 337 public void testLookupSubclass5() throws Throwable { 338 // S1 does not override setAccessible 339 Method m = S1.class.getMethod("setAccessible", boolean.class); 340 assertTrue(m.getDeclaringClass() == AccessibleObject.class); 341 MethodHandle mh = MethodHandles.lookup().unreflect(m); 342 mh.invoke(inaccessibleField(), true); // should throw InaccessibleObjectException 343 } 344 @Test(expectedExceptions = ClassCastException.class) 345 public void testLookupSubclass6() throws Throwable { 346 // S2 overrides setAccessible 347 Method m = S2.class.getMethod("setAccessible", boolean.class); 348 assertTrue(m.getDeclaringClass() == S2.class); 349 MethodHandle mh = MethodHandles.lookup().unreflect(m); 350 mh.invoke(accessibleField(), true); // should throw ClassCastException 351 } 352 @Test(expectedExceptions = ClassCastException.class) 353 public void testLookupSubclass7() throws Throwable { 354 // S2 overrides setAccessible 355 Method m = S2.class.getMethod("setAccessible", boolean.class); 356 assertTrue(m.getDeclaringClass() == S2.class); 357 MethodHandle mh = MethodHandles.lookup().unreflect(m); 358 mh.invoke(inaccessibleField(), true); // should throw ClassCastException 359 } 360 361 /** 362 * Field::getInt and Field::setInt are caller-sensitive methods. 363 * Test access to private field. 364 */ 365 private static int privateField = 0; 366 @Test 367 public void testPrivateField() throws Throwable { 368 Field f = CallerSensitiveAccess.class.getDeclaredField("privateField"); 369 // Field::setInt 370 MethodType mtype = MethodType.methodType(void.class, Object.class, int.class); 371 MethodHandle mh = MethodHandles.lookup().findVirtual(Field.class, "setInt", mtype); 372 mh.invokeExact(f, (Object)null, 5); 373 // Field::getInt 374 mh = MethodHandles.lookup().findVirtual(Field.class, "getInt", MethodType.methodType(int.class, Object.class)); 375 int value = (int)mh.invokeExact(f, (Object)null); 376 assertTrue(value == 5); 377 } 378 379 private static class Inner { 380 private static boolean privateField = false; 381 } 382 383 @Test 384 public void testInnerPrivateField() throws Throwable { 385 Field f = Inner.class.getDeclaredField("privateField"); 386 // Field::setInt 387 MethodType mtype = MethodType.methodType(void.class, Object.class, boolean.class); 388 MethodHandle mh = MethodHandles.lookup().findVirtual(Field.class, "setBoolean", mtype); 389 mh.invokeExact(f, (Object)null, true); 390 // Field::getInt 391 mh = MethodHandles.lookup().findVirtual(Field.class, "getBoolean", MethodType.methodType(boolean.class, Object.class)); 392 boolean value = (boolean)mh.invokeExact(f, (Object)null); 393 assertTrue(value); 394 } 395 396 // -- supporting methods -- 397 398 /** 399 * Returns a stream of all caller sensitive methods on public classes in packages 400 * exported by a named module. 401 */ 402 static Stream<Method> callerSensitiveMethods(Module module) { 403 assert 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); 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 }