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