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