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