1 /* 2 * Copyright (c) 2020, 2023, 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 /* 25 * @test 26 * @bug 8230501 27 * @library /test/lib 28 * @enablePreview 29 * @run testng/othervm ClassDataTest 30 */ 31 32 import java.io.IOException; 33 import java.io.OutputStream; 34 import java.io.UncheckedIOException; 35 import java.lang.classfile.ClassBuilder; 36 import java.lang.classfile.ClassFile; 37 import java.lang.classfile.TypeKind; 38 import java.lang.constant.ClassDesc; 39 import java.lang.constant.ConstantDescs; 40 import java.lang.constant.DirectMethodHandleDesc; 41 import java.lang.constant.DynamicConstantDesc; 42 import java.lang.constant.MethodTypeDesc; 43 import java.lang.invoke.MethodHandle; 44 import java.lang.invoke.MethodHandles; 45 import java.lang.invoke.MethodHandles.Lookup; 46 import java.lang.invoke.MethodType; 47 import java.lang.reflect.AccessFlag; 48 import java.lang.reflect.Method; 49 import java.nio.file.Files; 50 import java.nio.file.Path; 51 import java.nio.file.Paths; 52 import java.util.ArrayList; 53 import java.util.List; 54 import java.util.Map; 55 import java.util.function.Consumer; 56 import java.util.stream.Stream; 57 58 import org.testng.annotations.DataProvider; 59 import org.testng.annotations.Test; 60 61 import static java.lang.classfile.ClassFile.*; 62 import static java.lang.constant.ConstantDescs.*; 63 import static java.lang.invoke.MethodHandles.Lookup.*; 64 import static org.testng.Assert.*; 65 66 public class ClassDataTest { 67 private static final Lookup LOOKUP = MethodHandles.lookup(); 68 private static final ClassDesc CD_ClassDataTest = ClassDataTest.class.describeConstable().orElseThrow(); 69 70 @Test 71 public void testOriginalAccess() throws IllegalAccessException { 72 Lookup lookup = hiddenClass(20); 73 assertTrue(lookup.hasFullPrivilegeAccess()); 74 75 int value = MethodHandles.classData(lookup, "_", int.class); 76 assertEquals(value, 20); 77 78 Integer i = MethodHandles.classData(lookup, "_", Integer.class); 79 assertEquals(i.intValue(), 20); 80 } 81 82 /* 83 * A lookup class with no class data. 84 */ 85 @Test 86 public void noClassData() throws IllegalAccessException { 87 assertNull(MethodHandles.classData(LOOKUP, "_", Object.class)); 88 } 89 90 @DataProvider(name = "teleportedLookup") 91 private Object[][] teleportedLookup() throws ReflectiveOperationException { 92 Lookup lookup = hiddenClass(30); 93 Class<?> hc = lookup.lookupClass(); 94 assertClassData(lookup, 30); 95 96 int fullAccess = PUBLIC|PROTECTED|PACKAGE|MODULE|PRIVATE; 97 return new Object[][] { 98 new Object[] { MethodHandles.privateLookupIn(hc, LOOKUP), fullAccess}, 99 new Object[] { LOOKUP.in(hc), fullAccess & ~(PROTECTED|PRIVATE) }, 100 new Object[] { lookup.dropLookupMode(PRIVATE), fullAccess & ~(PROTECTED|PRIVATE) }, 101 }; 102 } 103 104 @Test(dataProvider = "teleportedLookup", expectedExceptions = { IllegalAccessException.class }) 105 public void illegalAccess(Lookup lookup, int access) throws IllegalAccessException { 106 int lookupModes = lookup.lookupModes(); 107 assertTrue((lookupModes & ORIGINAL) == 0); 108 assertEquals(lookupModes, access); 109 MethodHandles.classData(lookup, "_", int.class); 110 } 111 112 @Test(expectedExceptions = { ClassCastException.class }) 113 public void incorrectType() throws IllegalAccessException { 114 Lookup lookup = hiddenClass(20); 115 MethodHandles.classData(lookup, "_", Long.class); 116 } 117 118 @Test(expectedExceptions = { IndexOutOfBoundsException.class }) 119 public void invalidIndex() throws IllegalAccessException { 120 Lookup lookup = hiddenClass(List.of()); 121 MethodHandles.classDataAt(lookup, "_", Object.class, 0); 122 } 123 124 @Test(expectedExceptions = { NullPointerException.class }) 125 public void unboxNull() throws IllegalAccessException { 126 List<Integer> list = new ArrayList<>(); 127 list.add(null); 128 Lookup lookup = hiddenClass(list); 129 MethodHandles.classDataAt(lookup, "_", int.class, 0); 130 } 131 132 @Test 133 public void nullElement() throws IllegalAccessException { 134 List<Object> list = new ArrayList<>(); 135 list.add(null); 136 Lookup lookup = hiddenClass(list); 137 assertTrue(MethodHandles.classDataAt(lookup, "_", Object.class, 0) == null); 138 } 139 140 @Test 141 public void intClassData() throws ReflectiveOperationException { 142 ClassByteBuilder builder = new ClassByteBuilder("T1-int"); 143 byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, int.class).build(); 144 Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, 100, true); 145 int value = MethodHandles.classData(lookup, "_", int.class); 146 assertEquals(value, 100); 147 // call through condy 148 assertClassData(lookup, 100); 149 } 150 151 @Test 152 public void floatClassData() throws ReflectiveOperationException { 153 ClassByteBuilder builder = new ClassByteBuilder("T1-float"); 154 byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, float.class).build(); 155 Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, 0.1234f, true); 156 float value = MethodHandles.classData(lookup, "_", float.class); 157 assertEquals(value, 0.1234f); 158 // call through condy 159 assertClassData(lookup, 0.1234f); 160 } 161 162 @Test 163 public void classClassData() throws ReflectiveOperationException { 164 Class<?> hc = hiddenClass(100).lookupClass(); 165 ClassByteBuilder builder = new ClassByteBuilder("T2"); 166 byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, Class.class).build(); 167 Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, hc, true); 168 Class<?> value = MethodHandles.classData(lookup, "_", Class.class); 169 assertEquals(value, hc); 170 // call through condy 171 assertClassData(lookup, hc); 172 } 173 174 @Test 175 public void arrayClassData() throws ReflectiveOperationException { 176 ClassByteBuilder builder = new ClassByteBuilder("T3"); 177 byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, String[].class).build(); 178 String[] colors = new String[] { "red", "yellow", "blue"}; 179 Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, colors, true); 180 assertClassData(lookup, colors.clone()); 181 // class data is modifiable and not a constant 182 colors[0] = "black"; 183 // it will get back the modified class data 184 String[] value = MethodHandles.classData(lookup, "_", String[].class); 185 assertEquals(value, colors); 186 // even call through condy as it's not a constant 187 assertClassData(lookup, colors); 188 } 189 190 @Test 191 public void listClassData() throws ReflectiveOperationException { 192 ClassByteBuilder builder = new ClassByteBuilder("T4"); 193 byte[] bytes = builder.classDataAt(ACC_PUBLIC|ACC_STATIC, Integer.class, 2).build(); 194 List<Integer> cd = List.of(100, 101, 102, 103); 195 int expected = 102; // element at index=2 196 Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, cd, true); 197 int value = MethodHandles.classDataAt(lookup, "_", int.class, 2); 198 assertEquals(value, expected); 199 // call through condy 200 assertClassData(lookup, expected); 201 } 202 203 @Test 204 public void arrayListClassData() throws ReflectiveOperationException { 205 ClassByteBuilder builder = new ClassByteBuilder("T4"); 206 byte[] bytes = builder.classDataAt(ACC_PUBLIC|ACC_STATIC, Integer.class, 1).build(); 207 ArrayList<Integer> cd = new ArrayList<>(); 208 Stream.of(100, 101, 102, 103).forEach(cd::add); 209 int expected = 101; // element at index=1 210 Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, cd, true); 211 int value = MethodHandles.classDataAt(lookup, "_", int.class, 1); 212 assertEquals(value, expected); 213 // call through condy 214 assertClassData(lookup, expected); 215 } 216 217 private static Lookup hiddenClass(int value) { 218 ClassByteBuilder builder = new ClassByteBuilder("HC"); 219 byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, int.class).build(); 220 try { 221 return LOOKUP.defineHiddenClassWithClassData(bytes, value, true); 222 } catch (Throwable e) { 223 throw new RuntimeException(e); 224 } 225 } 226 private static Lookup hiddenClass(List<?> list) { 227 ClassByteBuilder builder = new ClassByteBuilder("HC"); 228 byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, List.class).build(); 229 try { 230 return LOOKUP.defineHiddenClassWithClassData(bytes, list, true); 231 } catch (Throwable e) { 232 throw new RuntimeException(e); 233 } 234 } 235 236 @Test 237 public void condyInvokedFromVirtualMethod() throws ReflectiveOperationException { 238 ClassByteBuilder builder = new ClassByteBuilder("T5"); 239 // generate classData instance method 240 byte[] bytes = builder.classData(ACC_PUBLIC, Class.class).build(); 241 Lookup hcLookup = hiddenClass(100); 242 assertClassData(hcLookup, 100); 243 Class<?> hc = hcLookup.lookupClass(); 244 Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, hc, true); 245 Class<?> value = MethodHandles.classData(lookup, "_", Class.class); 246 assertEquals(value, hc); 247 // call through condy 248 Class<?> c = lookup.lookupClass(); 249 assertClassData(lookup, c.newInstance(), hc); 250 } 251 252 @Test 253 public void immutableListClassData() throws ReflectiveOperationException { 254 ClassByteBuilder builder = new ClassByteBuilder("T6"); 255 // generate classDataAt instance method 256 byte[] bytes = builder.classDataAt(ACC_PUBLIC, Integer.class, 2).build(); 257 List<Integer> cd = List.of(100, 101, 102, 103); 258 int expected = 102; // element at index=2 259 Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, cd, true); 260 int value = MethodHandles.classDataAt(lookup, "_", int.class, 2); 261 assertEquals(value, expected); 262 // call through condy 263 Class<?> c = lookup.lookupClass(); 264 assertClassData(lookup, c.newInstance() ,expected); 265 } 266 267 /* 268 * The return value of MethodHandles::classDataAt is the element 269 * contained in the list when the method is called. 270 * If MethodHandles::classDataAt is called via condy, the value 271 * will be captured as a constant. If the class data is modified 272 * after the element at the given index is computed via condy, 273 * subsequent LDC of such ConstantDynamic entry will return the same 274 * value. However, direct invocation of MethodHandles::classDataAt 275 * will return the modified value. 276 */ 277 @Test 278 public void mutableListClassData() throws ReflectiveOperationException { 279 ClassByteBuilder builder = new ClassByteBuilder("T7"); 280 // generate classDataAt instance method 281 byte[] bytes = builder.classDataAt(ACC_PUBLIC, MethodType.class, 0).build(); 282 MethodType mtype = MethodType.methodType(int.class, String.class); 283 List<MethodType> cd = new ArrayList<>(List.of(mtype)); 284 Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, cd, true); 285 // call through condy 286 Class<?> c = lookup.lookupClass(); 287 assertClassData(lookup, c.newInstance(), mtype); 288 // modify the class data 289 assertTrue(cd.remove(0) == mtype); 290 cd.add(0, MethodType.methodType(void.class)); 291 MethodType newMType = cd.get(0); 292 // loading the element using condy returns the original value 293 assertClassData(lookup, c.newInstance(), mtype); 294 // direct invocation of MethodHandles.classDataAt returns the modified value 295 assertEquals(MethodHandles.classDataAt(lookup, "_", MethodType.class, 0), newMType); 296 } 297 298 // helper method to extract from a class data map 299 public static <T> T getClassDataEntry(Lookup lookup, String key, Class<T> type) throws IllegalAccessException { 300 Map<String, T> cd = MethodHandles.classData(lookup, "_", Map.class); 301 return type.cast(cd.get(key)); 302 } 303 304 @Test 305 public void classDataMap() throws ReflectiveOperationException { 306 ClassByteBuilder builder = new ClassByteBuilder("map"); 307 // generate classData static method 308 DirectMethodHandleDesc bsm = ConstantDescs.ofConstantBootstrap(CD_ClassDataTest, "getClassDataEntry", CD_Object); 309 // generate two accessor methods to get the entries from class data 310 byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, Map.class) 311 .classData(ACC_PUBLIC|ACC_STATIC, "getClass", 312 Class.class, DynamicConstantDesc.ofNamed(bsm, "class", CD_Class)) 313 .classData(ACC_PUBLIC|ACC_STATIC, "getMethod", 314 MethodHandle.class, DynamicConstantDesc.ofNamed(bsm, "method", CD_MethodHandle)) 315 .build(); 316 317 // generate a hidden class 318 Lookup hcLookup = hiddenClass(100); 319 Class<?> hc = hcLookup.lookupClass(); 320 assertClassData(hcLookup, 100); 321 322 MethodHandle mh = hcLookup.findStatic(hc, "classData", MethodType.methodType(int.class)); 323 Map<String, Object> cd = Map.of("class", hc, "method", mh); 324 Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, cd, true); 325 assertClassData(lookup, cd); 326 327 // validate the entries from the class data map 328 Class<?> c = lookup.lookupClass(); 329 Method m = c.getMethod("getClass"); 330 Class<?> v = (Class<?>)m.invoke(null); 331 assertEquals(hc, v); 332 333 Method m1 = c.getMethod("getMethod"); 334 MethodHandle v1 = (MethodHandle) m1.invoke(null); 335 assertEquals(mh, v1); 336 } 337 338 @Test(expectedExceptions = { IllegalArgumentException.class }) 339 public void nonDefaultName() throws ReflectiveOperationException { 340 ClassByteBuilder builder = new ClassByteBuilder("nonDefaultName"); 341 byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, Class.class) 342 .build(); 343 Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, ClassDataTest.class, true); 344 assertClassData(lookup, ClassDataTest.class); 345 // throw IAE 346 MethodHandles.classData(lookup, "non_default_name", Class.class); 347 } 348 349 static class ClassByteBuilder { 350 private static final String OBJECT_CLS = "java/lang/Object"; 351 private static final String MHS_CLS = "java/lang/invoke/MethodHandles"; 352 private static final String CLASS_DATA_BSM_DESCR = 353 "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"; 354 private Consumer<ClassBuilder> cw; 355 private final ClassDesc classname; 356 357 /** 358 * A builder to generate a class file to access class data 359 * @param classname 360 */ 361 ClassByteBuilder(String classname) { 362 this.classname = ClassDesc.ofInternalName(classname); 363 this.cw = clb -> { 364 clb.withSuperclass(CD_Object); 365 clb.withFlags(AccessFlag.FINAL); 366 clb.withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> { 367 cob.aload(0); 368 cob.invokespecial(CD_Object, INIT_NAME, MTD_void); 369 cob.return_(); 370 }); 371 }; 372 } 373 374 byte[] build() { 375 byte[] bytes = ClassFile.of().build(classname, cw); 376 Path p = Paths.get(classname + ".class"); 377 try (OutputStream os = Files.newOutputStream(p)) { 378 os.write(bytes); 379 } catch (IOException e) { 380 throw new UncheckedIOException(e); 381 } 382 return bytes; 383 } 384 385 /* 386 * Generate classData method to load class data via condy 387 */ 388 ClassByteBuilder classData(int accessFlags, Class<?> returnType) { 389 ClassDesc returnDesc = returnType.describeConstable().orElseThrow(); 390 MethodTypeDesc mt = MethodTypeDesc.of(returnDesc); 391 cw = cw.andThen(clb -> { 392 clb.withMethodBody("classData", mt, accessFlags, cob -> { 393 cob.loadConstant(DynamicConstantDesc.ofNamed(BSM_CLASS_DATA, DEFAULT_NAME, returnDesc)); 394 cob.return_(TypeKind.from(returnType)); 395 }); 396 }); 397 return this; 398 } 399 400 /* 401 * Generate classDataAt method to load an element from class data via condy 402 */ 403 ClassByteBuilder classDataAt(int accessFlags, Class<?> returnType, int index) { 404 ClassDesc returnDesc = returnType.describeConstable().orElseThrow(); 405 MethodTypeDesc mt = MethodTypeDesc.of(returnDesc); 406 cw = cw.andThen(clb -> { 407 clb.withMethodBody("classData", mt, accessFlags, cob -> { 408 cob.loadConstant(DynamicConstantDesc.ofNamed(BSM_CLASS_DATA_AT, DEFAULT_NAME, returnDesc, index)); 409 cob.return_(TypeKind.from(returnType)); 410 }); 411 }); 412 return this; 413 } 414 415 ClassByteBuilder classData(int accessFlags, String name, Class<?> returnType, DynamicConstantDesc<?> dynamic) { 416 ClassDesc returnDesc = returnType.describeConstable().orElseThrow(); 417 MethodTypeDesc mt = MethodTypeDesc.of(returnDesc); 418 cw = cw.andThen(clb -> { 419 clb.withMethodBody(name, mt, accessFlags, cob -> { 420 cob.loadConstant(dynamic); 421 cob.return_(TypeKind.from(returnType)); 422 }); 423 }); 424 return this; 425 } 426 } 427 428 /* 429 * Load an int constant from class data via condy and 430 * verify it matches the given value. 431 */ 432 private void assertClassData(Lookup lookup, int value) throws ReflectiveOperationException { 433 Class<?> c = lookup.lookupClass(); 434 Method m = c.getMethod("classData"); 435 int v = (int) m.invoke(null); 436 assertEquals(value, v); 437 } 438 439 /* 440 * Load an int constant from class data via condy and 441 * verify it matches the given value. 442 */ 443 private void assertClassData(Lookup lookup, Object o, int value) throws ReflectiveOperationException { 444 Class<?> c = lookup.lookupClass(); 445 Method m = c.getMethod("classData"); 446 int v = (int) m.invoke(o); 447 assertEquals(value, v); 448 } 449 450 /* 451 * Load a float constant from class data via condy and 452 * verify it matches the given value. 453 */ 454 private void assertClassData(Lookup lookup, float value) throws ReflectiveOperationException { 455 Class<?> c = lookup.lookupClass(); 456 Method m = c.getMethod("classData"); 457 float v = (float) m.invoke(null); 458 assertEquals(value, v); 459 } 460 461 /* 462 * Load a Class constant from class data via condy and 463 * verify it matches the given value. 464 */ 465 private void assertClassData(Lookup lookup, Class<?> value) throws ReflectiveOperationException { 466 Class<?> c = lookup.lookupClass(); 467 Method m = c.getMethod("classData"); 468 Class<?> v = (Class<?>)m.invoke(null); 469 assertEquals(value, v); 470 } 471 472 /* 473 * Load a Class from class data via condy and 474 * verify it matches the given value. 475 */ 476 private void assertClassData(Lookup lookup, Object o, Class<?> value) throws ReflectiveOperationException { 477 Class<?> c = lookup.lookupClass(); 478 Method m = c.getMethod("classData"); 479 Object v = m.invoke(o); 480 assertEquals(value, v); 481 } 482 483 /* 484 * Load an Object from class data via condy and 485 * verify it matches the given value. 486 */ 487 private void assertClassData(Lookup lookup, Object value) throws ReflectiveOperationException { 488 Class<?> c = lookup.lookupClass(); 489 Method m = c.getMethod("classData"); 490 Object v = m.invoke(null); 491 assertEquals(value, v); 492 } 493 494 /* 495 * Load an Object from class data via condy and 496 * verify it matches the given value. 497 */ 498 private void assertClassData(Lookup lookup, Object o, Object value) throws ReflectiveOperationException { 499 Class<?> c = lookup.lookupClass(); 500 Method m = c.getMethod("classData"); 501 Object v = m.invoke(o); 502 assertEquals(value, v); 503 } 504 }