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