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