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