rev 58768 : 8238358: Implementation of JEP 371: Hidden Classes Reviewed-by: alanb, cjplummer, coleenp, dholmes, dlong, forax, jlahoda, psandoz, plevart, vromero Contributed-by: mandy.chung@oracle.com, lois.foltan@oracle.com, david.holmes@oracle.com, harold.seigel@oracle.com, serguei.spitsyn@oracle.com, alex.buckley@oracle.com, jamsheed.c.m@oracle.com, jan.lahoda@oracle.com, amy.lu@oracle.com rev 58769 : imported patch type-descriptor-name rev 58770 : [mq]: svc-spec-update
1 /* 2 * Copyright (c) 2019, 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 * @modules java.base/jdk.internal.org.objectweb.asm 27 * jdk.compiler 28 * @library /test/lib 29 * @build jdk.test.lib.Utils 30 * jdk.test.lib.compiler.CompilerUtils 31 * @run testng/othervm --enable-preview BasicTest 32 */ 33 34 import java.io.File; 35 import java.io.IOException; 36 import java.lang.invoke.MethodHandles.Lookup; 37 38 import static java.lang.invoke.MethodHandles.lookup; 39 import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; 40 41 import java.lang.reflect.Array; 42 import java.lang.reflect.Method; 43 import java.nio.charset.StandardCharsets; 44 import java.nio.file.Files; 45 import java.nio.file.Path; 46 import java.nio.file.Paths; 47 import java.util.Arrays; 48 import java.util.List; 49 import java.util.stream.Stream; 50 51 import jdk.internal.org.objectweb.asm.ClassWriter; 52 import jdk.internal.org.objectweb.asm.Type; 53 import jdk.test.lib.compiler.CompilerUtils; 54 import jdk.test.lib.Utils; 55 56 import org.testng.annotations.BeforeTest; 57 import org.testng.annotations.DataProvider; 58 import org.testng.annotations.Test; 59 60 import static jdk.internal.org.objectweb.asm.Opcodes.*; 61 import static org.testng.Assert.*; 62 63 interface HiddenTest { 64 void test(); 65 } 66 67 public class BasicTest { 68 69 private static final Path SRC_DIR = Paths.get(Utils.TEST_SRC, "src"); 70 private static final Path CLASSES_DIR = Paths.get("classes"); 71 private static final Path CLASSES_10_DIR = Paths.get("classes_10"); 72 73 private static byte[] hiddenClassBytes; 74 75 @BeforeTest 76 static void setup() throws IOException { 77 compileSources(SRC_DIR, CLASSES_DIR, 78 "--enable-preview", "-source", String.valueOf(Runtime.version().feature())); 79 hiddenClassBytes = Files.readAllBytes(CLASSES_DIR.resolve("HiddenClass.class")); 80 81 // compile with --release 10 with no NestHost and NestMembers attribute 82 compileSources(SRC_DIR.resolve("Outer.java"), CLASSES_10_DIR, "--release", "10"); 83 compileSources(SRC_DIR.resolve("EnclosingClass.java"), CLASSES_10_DIR, "--release", "10"); 84 } 85 86 static void compileSources(Path sourceFile, Path dest, String... options) throws IOException { 87 Stream<String> ops = Stream.of("-cp", Utils.TEST_CLASSES + File.pathSeparator + CLASSES_DIR); 88 if (options != null && options.length > 0) { 89 ops = Stream.concat(ops, Arrays.stream(options)); 90 } 91 if (!CompilerUtils.compile(sourceFile, dest, ops.toArray(String[]::new))) { 92 throw new RuntimeException("Compilation of the test failed: " + sourceFile); 93 } 94 } 95 96 static Class<?> defineHiddenClass(String name) throws Exception { 97 byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class")); 98 Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass(); 99 assertHiddenClass(hc); 100 singletonNest(hc); 101 return hc; 102 } 103 104 // basic test on a hidden class 105 @Test 106 public void hiddenClass() throws Throwable { 107 HiddenTest t = (HiddenTest)defineHiddenClass("HiddenClass").newInstance(); 108 t.test(); 109 110 // sanity check 111 Class<?> c = t.getClass(); 112 Class<?>[] intfs = c.getInterfaces(); 113 assertTrue(c.isHidden()); 114 assertFalse(c.isPrimitive()); 115 assertTrue(intfs.length == 1); 116 assertTrue(intfs[0] == HiddenTest.class); 117 assertTrue(c.getCanonicalName() == null); 118 119 String hcName = "HiddenClass"; 120 String hcSuffix = "0x[0-9a-f]+"; 121 assertTrue(c.getName().matches(hcName + "/" + hcSuffix)); 122 assertTrue(c.descriptorString().matches("L" + hcName + ";" + "/" + hcSuffix)); 123 124 // test array of hidden class 125 testHiddenArray(c); 126 127 // test setAccessible 128 checkSetAccessible(c, "realTest"); 129 checkSetAccessible(c, "test"); 130 } 131 132 // primitive class is not a hidden class 133 @Test 134 public void primitiveClass() { 135 assertFalse(int.class.isHidden()); 136 assertFalse(String.class.isHidden()); 137 } 138 139 private void testHiddenArray(Class<?> type) throws Exception { 140 // array of hidden class 141 Object array = Array.newInstance(type, 2); 142 Class<?> arrayType = array.getClass(); 143 assertTrue(arrayType.isArray()); 144 assertTrue(Array.getLength(array) == 2); 145 assertFalse(arrayType.isHidden()); 146 147 String hcName = "HiddenClass"; 148 String hcSuffix = "0x[0-9a-f]+"; 149 assertTrue(arrayType.getName().matches("\\[" + "L" + hcName + "/" + hcSuffix + ";")); 150 assertTrue(arrayType.descriptorString().matches("\\[" + "L" + hcName + ";" + "/" + hcSuffix)); 151 152 assertTrue(arrayType.getComponentType().isHidden()); 153 assertTrue(arrayType.getComponentType() == type); 154 Object t = type.newInstance(); 155 Array.set(array, 0, t); 156 Object o = Array.get(array, 0); 157 assertTrue(o == t); 158 } 159 160 private void checkSetAccessible(Class<?> c, String name, Class<?>... ptypes) throws Exception { 161 Method m = c.getDeclaredMethod(name, ptypes); 162 assertTrue(m.trySetAccessible()); 163 m.setAccessible(true); 164 } 165 166 // Define a hidden class that uses lambda 167 // This verifies LambdaMetaFactory supports the caller which is a hidden class 168 @Test 169 public void testLambda() throws Throwable { 170 HiddenTest t = (HiddenTest)defineHiddenClass("Lambda").newInstance(); 171 try { 172 t.test(); 173 } catch (Error e) { 174 if (!e.getMessage().equals("thrown by " + t.getClass().getName())) { 175 throw e; 176 } 177 } 178 } 179 180 // Verify the nest host and nest members of a hidden class and hidden nestmate class 181 @Test 182 public void testHiddenNestHost() throws Throwable { 183 byte[] hc1 = hiddenClassBytes; 184 Lookup lookup1 = lookup().defineHiddenClass(hc1, false); 185 Class<?> host = lookup1.lookupClass(); 186 187 byte[] hc2 = Files.readAllBytes(CLASSES_DIR.resolve("Lambda.class")); 188 Lookup lookup2 = lookup1.defineHiddenClass(hc2, false, NESTMATE); 189 Class<?> member = lookup2.lookupClass(); 190 191 // test nest membership and reflection API 192 assertTrue(host.isNestmateOf(member)); 193 assertTrue(host.getNestHost() == host); 194 // getNestHost and getNestMembers return the same value when calling 195 // on a nest member and the nest host 196 assertTrue(member.getNestHost() == host.getNestHost()); 197 assertTrue(Arrays.equals(member.getNestMembers(), host.getNestMembers())); 198 // getNestMembers includes the nest host that can be a hidden class but 199 // only includes static nest members 200 assertTrue(host.getNestMembers().length == 1); 201 assertTrue(host.getNestMembers()[0] == host); 202 } 203 204 @DataProvider(name = "hiddenClasses") 205 private Object[][] hiddenClasses() { 206 return new Object[][] { 207 new Object[] { "HiddenInterface", false }, 208 new Object[] { "AbstractClass", false }, 209 // a hidden annotation is useless because it cannot be referenced by any class 210 new Object[] { "HiddenAnnotation", false }, 211 // class file with bad NestHost, NestMembers and InnerClasses or EnclosingMethod attribute 212 // define them as nestmate to verify Class::getNestHost and getNestMembers 213 new Object[] { "Outer", true }, 214 new Object[] { "Outer$Inner", true }, 215 new Object[] { "EnclosingClass", true }, 216 new Object[] { "EnclosingClass$1", true }, 217 }; 218 } 219 220 /* 221 * Test that class file bytes that can be defined as a normal class 222 * can be successfully created as a hidden class even it might not 223 * make sense as a hidden class. For example, a hidden annotation 224 * is not useful as it cannot be referenced and an outer/inner class 225 * when defined as a hidden effectively becomes a final top-level class. 226 */ 227 @Test(dataProvider = "hiddenClasses") 228 public void defineHiddenClass(String name, boolean nestmate) throws Exception { 229 byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class")); 230 Class<?> hc; 231 Class<?> host; 232 if (nestmate) { 233 hc = lookup().defineHiddenClass(bytes, false, NESTMATE).lookupClass(); 234 host = lookup().lookupClass().getNestHost(); 235 } else { 236 hc = lookup().defineHiddenClass(bytes, false).lookupClass(); 237 host = hc; 238 } 239 assertTrue(hc.getNestHost() == host); 240 assertTrue(hc.getNestMembers().length == 1); 241 assertTrue(hc.getNestMembers()[0] == host); 242 } 243 244 @DataProvider(name = "emptyClasses") 245 private Object[][] emptyClasses() { 246 return new Object[][] { 247 new Object[] { "EmptyHiddenSynthetic", ACC_SYNTHETIC }, 248 new Object[] { "EmptyHiddenEnum", ACC_ENUM }, 249 new Object[] { "EmptyHiddenAbstractClass", ACC_ABSTRACT }, 250 new Object[] { "EmptyHiddenInterface", ACC_ABSTRACT|ACC_INTERFACE }, 251 new Object[] { "EmptyHiddenAnnotation", ACC_ANNOTATION|ACC_ABSTRACT|ACC_INTERFACE }, 252 }; 253 } 254 255 /* 256 * Test if an empty class with valid access flags can be created as a hidden class 257 * as long as it does not violate the restriction of a hidden class. 258 * 259 * A meaningful enum type defines constants of that enum type. So 260 * enum class containing constants of its type should not be a hidden 261 * class. 262 */ 263 @Test(dataProvider = "emptyClasses") 264 public void emptyHiddenClass(String name, int accessFlags) throws Exception { 265 byte[] bytes = (accessFlags == ACC_ENUM) ? classBytes(name, Enum.class, accessFlags) 266 : classBytes(name, accessFlags); 267 Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass(); 268 switch (accessFlags) { 269 case ACC_SYNTHETIC: 270 assertTrue(hc.isSynthetic()); 271 assertFalse(hc.isEnum()); 272 assertFalse(hc.isAnnotation()); 273 assertFalse(hc.isInterface()); 274 break; 275 case ACC_ENUM: 276 assertFalse(hc.isSynthetic()); 277 assertTrue(hc.isEnum()); 278 assertFalse(hc.isAnnotation()); 279 assertFalse(hc.isInterface()); 280 break; 281 case ACC_ABSTRACT: 282 assertFalse(hc.isSynthetic()); 283 assertFalse(hc.isEnum()); 284 assertFalse(hc.isAnnotation()); 285 assertFalse(hc.isInterface()); 286 break; 287 case ACC_ABSTRACT|ACC_INTERFACE: 288 assertFalse(hc.isSynthetic()); 289 assertFalse(hc.isEnum()); 290 assertFalse(hc.isAnnotation()); 291 assertTrue(hc.isInterface()); 292 break; 293 case ACC_ANNOTATION|ACC_ABSTRACT|ACC_INTERFACE: 294 assertFalse(hc.isSynthetic()); 295 assertFalse(hc.isEnum()); 296 assertTrue(hc.isAnnotation()); 297 assertTrue(hc.isInterface()); 298 break; 299 default: 300 throw new IllegalArgumentException("unexpected access flag: " + accessFlags); 301 } 302 assertTrue(hc.isHidden()); 303 assertTrue(hc.getModifiers() == (ACC_PUBLIC|accessFlags)); 304 assertFalse(hc.isLocalClass()); 305 assertFalse(hc.isMemberClass()); 306 assertFalse(hc.isAnonymousClass()); 307 assertFalse(hc.isArray()); 308 } 309 310 // These class files can't be defined as hidden classes 311 @DataProvider(name = "cantBeHiddenClasses") 312 private Object[][] cantBeHiddenClasses() { 313 return new Object[][] { 314 // a hidden class can't be a field's declaring type 315 // enum class with static final HiddenEnum[] $VALUES: 316 new Object[] { "HiddenEnum" }, 317 // supertype of this class is a hidden class 318 new Object[] { "HiddenSuper" }, 319 // a record class whose equals(HiddenRecord, Object) method 320 // refers to a hidden class in the parameter type and fails 321 // verification. Perhaps this method signature should be reconsidered. 322 new Object[] { "HiddenRecord" }, 323 }; 324 } 325 326 /* 327 * These class files 328 */ 329 @Test(dataProvider = "cantBeHiddenClasses", expectedExceptions = NoClassDefFoundError.class) 330 public void failToDeriveAsHiddenClass(String name) throws Exception { 331 byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve(name + ".class")); 332 Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass(); 333 } 334 335 /* 336 * A hidden class can be successfully created but fails to be reflected 337 * if it refers to its own type in the descriptor. 338 * e.g. Class::getMethods resolves the declaring type of fields, 339 * parameter types and return type. 340 */ 341 @Test 342 public void hiddenCantReflect() throws Throwable { 343 HiddenTest t = (HiddenTest)defineHiddenClass("HiddenCantReflect").newInstance(); 344 t.test(); 345 346 Class<?> c = t.getClass(); 347 Class<?>[] intfs = c.getInterfaces(); 348 assertTrue(intfs.length == 1); 349 assertTrue(intfs[0] == HiddenTest.class); 350 351 try { 352 // this would cause loading of class HiddenCantReflect and NCDFE due 353 // to error during verification 354 c.getDeclaredMethods(); 355 } catch (NoClassDefFoundError e) { 356 Throwable x = e.getCause(); 357 if (x == null || !(x instanceof ClassNotFoundException && x.getMessage().contains("HiddenCantReflect"))) { 358 throw e; 359 } 360 } 361 } 362 363 @Test(expectedExceptions = {IllegalArgumentException.class}) 364 public void cantDefineModule() throws Throwable { 365 Path src = Paths.get("module-info.java"); 366 Path dir = CLASSES_DIR.resolve("m"); 367 Files.write(src, List.of("module m {}"), StandardCharsets.UTF_8); 368 compileSources(src, dir); 369 370 byte[] bytes = Files.readAllBytes(dir.resolve("module-info.class")); 371 lookup().defineHiddenClass(bytes, false); 372 } 373 374 @Test(expectedExceptions = {IllegalArgumentException.class}) 375 public void cantDefineClassInAnotherPackage() throws Throwable { 376 Path src = Paths.get("ClassInAnotherPackage.java"); 377 Files.write(src, List.of("package p;", "public class ClassInAnotherPackage {}"), StandardCharsets.UTF_8); 378 compileSources(src, CLASSES_DIR); 379 380 byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("p").resolve("ClassInAnotherPackage.class")); 381 lookup().defineHiddenClass(bytes, false); 382 } 383 384 @Test(expectedExceptions = {IllegalAccessException.class}) 385 public void lessPrivilegedLookup() throws Throwable { 386 Lookup lookup = lookup().dropLookupMode(Lookup.PRIVATE); 387 lookup.defineHiddenClass(hiddenClassBytes, false); 388 } 389 390 @DataProvider(name = "nestedTypesOrAnonymousClass") 391 private Object[][] nestedTypesOrAnonymousClass() { 392 return new Object[][] { 393 // class file with bad InnerClasses or EnclosingMethod attribute 394 new Object[] { "Outer", null }, 395 new Object[] { "Outer$Inner", "Outer" }, 396 new Object[] { "EnclosingClass", null }, 397 new Object[] { "EnclosingClass$1", "EnclosingClass" }, 398 }; 399 } 400 401 @Test(dataProvider = "nestedTypesOrAnonymousClass") 402 public void hasInnerClassesOrEnclosingMethodAttribute(String className, String badDeclaringClassName) throws Throwable { 403 byte[] bytes = Files.readAllBytes(CLASSES_10_DIR.resolve(className + ".class")); 404 Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass(); 405 hiddenClassWithBadAttribute(hc, badDeclaringClassName); 406 } 407 408 // define a hidden class with static nest membership 409 @Test 410 public void hasStaticNestHost() throws Exception { 411 byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("Outer$Inner.class")); 412 Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass(); 413 hiddenClassWithBadAttribute(hc, "Outer"); 414 } 415 416 @Test 417 public void hasStaticNestMembers() throws Throwable { 418 byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("Outer.class")); 419 Class<?> hc = lookup().defineHiddenClass(bytes, false).lookupClass(); 420 assertHiddenClass(hc); 421 assertTrue(hc.getNestHost() == hc); 422 Class<?>[] members = hc.getNestMembers(); 423 assertTrue(members.length == 1 && members[0] == hc); 424 } 425 426 // a hidden class with bad InnerClasses or EnclosingMethod attribute 427 private void hiddenClassWithBadAttribute(Class<?> hc, String badDeclaringClassName) { 428 assertTrue(hc.isHidden()); 429 assertTrue(hc.getCanonicalName() == null); 430 assertTrue(hc.getName().contains("/")); 431 432 if (badDeclaringClassName == null) { 433 // the following reflection API assumes a good name in InnerClasses 434 // or EnclosingMethod attribute can successfully be resolved. 435 assertTrue(hc.getSimpleName().length() > 0); 436 assertFalse(hc.isAnonymousClass()); 437 assertFalse(hc.isLocalClass()); 438 assertFalse(hc.isMemberClass()); 439 } else { 440 declaringClassNotFound(hc, badDeclaringClassName); 441 } 442 443 // validation of nest membership 444 assertTrue(hc.getNestHost() == hc); 445 // validate the static nest membership 446 Class<?>[] members = hc.getNestMembers(); 447 assertTrue(members.length == 1 && members[0] == hc); 448 } 449 450 // Class::getSimpleName, Class::isMemberClass 451 private void declaringClassNotFound(Class<?> c, String cn) { 452 try { 453 // fail to find declaring/enclosing class 454 c.isMemberClass(); 455 assertTrue(false); 456 } catch (NoClassDefFoundError e) { 457 if (!e.getMessage().equals(cn)) { 458 throw e; 459 } 460 } 461 try { 462 // fail to find declaring/enclosing class 463 c.getSimpleName(); 464 assertTrue(false); 465 } catch (NoClassDefFoundError e) { 466 if (!e.getMessage().equals(cn)) { 467 throw e; 468 } 469 } 470 } 471 472 private static void singletonNest(Class<?> hc) { 473 assertTrue(hc.getNestHost() == hc); 474 assertTrue(hc.getNestMembers().length == 1); 475 assertTrue(hc.getNestMembers()[0] == hc); 476 } 477 478 private static void assertHiddenClass(Class<?> hc) { 479 assertTrue(hc.isHidden()); 480 assertTrue(hc.getCanonicalName() == null); 481 assertTrue(hc.getName().contains("/")); 482 assertFalse(hc.isAnonymousClass()); 483 assertFalse(hc.isLocalClass()); 484 assertFalse(hc.isMemberClass()); 485 assertFalse(hc.getSimpleName().isEmpty()); // sanity check 486 } 487 488 private static byte[] classBytes(String classname, int accessFlags) { 489 return classBytes(classname, Object.class, accessFlags); 490 } 491 492 private static byte[] classBytes(String classname, Class<?> supertType, int accessFlags) { 493 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); 494 cw.visit(V14, ACC_PUBLIC|accessFlags, classname, null, Type.getInternalName(supertType), null); 495 cw.visitEnd(); 496 497 return cw.toByteArray(); 498 } 499 } --- EOF ---